"""
RBAC Unit Tests
===============

Tests for the Role-Based Access Control system:
- SystemRole hierarchy
- has_role() method
- set_role() with is_admin sync
- Permission helper methods (can_access_admin_panel, can_manage_users, etc.)
- role_required decorator
"""

import os
import sys
from unittest.mock import MagicMock, patch

import pytest

# Add project root to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))

from database import SystemRole, CompanyRole, User


# ============================================================
# SystemRole Hierarchy Tests
# ============================================================

class TestSystemRoleHierarchy:
    """Test that SystemRole enum values enforce correct hierarchy."""

    def test_role_ordering(self):
        assert SystemRole.UNAFFILIATED < SystemRole.MEMBER
        assert SystemRole.MEMBER < SystemRole.EMPLOYEE
        assert SystemRole.EMPLOYEE < SystemRole.MANAGER
        assert SystemRole.MANAGER < SystemRole.OFFICE_MANAGER
        assert SystemRole.OFFICE_MANAGER < SystemRole.ADMIN

    def test_role_values(self):
        assert SystemRole.UNAFFILIATED == 10
        assert SystemRole.MEMBER == 20
        assert SystemRole.EMPLOYEE == 30
        assert SystemRole.MANAGER == 40
        assert SystemRole.OFFICE_MANAGER == 50
        assert SystemRole.ADMIN == 100

    def test_from_string_valid(self):
        assert SystemRole.from_string('ADMIN') == SystemRole.ADMIN
        assert SystemRole.from_string('OFFICE_MANAGER') == SystemRole.OFFICE_MANAGER
        assert SystemRole.from_string('MEMBER') == SystemRole.MEMBER

    def test_from_string_case_insensitive(self):
        assert SystemRole.from_string('admin') == SystemRole.ADMIN
        assert SystemRole.from_string('member') == SystemRole.MEMBER

    def test_from_string_invalid_defaults_to_unaffiliated(self):
        assert SystemRole.from_string('INVALID') == SystemRole.UNAFFILIATED
        assert SystemRole.from_string('') == SystemRole.UNAFFILIATED

    def test_enum_by_name(self):
        assert SystemRole['ADMIN'] == SystemRole.ADMIN
        assert SystemRole['MEMBER'] == SystemRole.MEMBER
        with pytest.raises(KeyError):
            SystemRole['INVALID']


# ============================================================
# User.has_role() Tests
# ============================================================

def _make_user(role_name='MEMBER', is_admin=False):
    """Create a fake user object with User methods for testing RBAC logic.

    Cannot use User() directly since SA instrumented attributes require
    a mapped session. Instead, use a plain object with User's methods bound.
    """
    class FakeUser:
        pass

    user = FakeUser()
    user.role = role_name
    user.is_admin = is_admin
    user.company_role = 'NONE'
    user.company_id = None

    # Bind User's property and methods
    user.system_role = User.system_role.fget(user)
    user.has_role = lambda required_role: User.has_role(user, required_role)
    user.can_access_admin_panel = lambda: User.can_access_admin_panel(user)
    user.can_manage_users = lambda: User.can_manage_users(user)
    user.can_moderate_forum = lambda: User.can_moderate_forum(user)
    return user


class TestHasRole:
    """Test User.has_role() hierarchical check."""

    def test_admin_has_all_roles(self):
        user = _make_user('ADMIN')
        assert user.has_role(SystemRole.ADMIN)
        assert user.has_role(SystemRole.OFFICE_MANAGER)
        assert user.has_role(SystemRole.MANAGER)
        assert user.has_role(SystemRole.EMPLOYEE)
        assert user.has_role(SystemRole.MEMBER)
        assert user.has_role(SystemRole.UNAFFILIATED)

    def test_office_manager_cannot_access_admin(self):
        user = _make_user('OFFICE_MANAGER')
        assert not user.has_role(SystemRole.ADMIN)
        assert user.has_role(SystemRole.OFFICE_MANAGER)
        assert user.has_role(SystemRole.MANAGER)
        assert user.has_role(SystemRole.MEMBER)

    def test_member_minimal_access(self):
        user = _make_user('MEMBER')
        assert not user.has_role(SystemRole.ADMIN)
        assert not user.has_role(SystemRole.OFFICE_MANAGER)
        assert not user.has_role(SystemRole.MANAGER)
        assert not user.has_role(SystemRole.EMPLOYEE)
        assert user.has_role(SystemRole.MEMBER)
        assert user.has_role(SystemRole.UNAFFILIATED)

    def test_unaffiliated_lowest_access(self):
        user = _make_user('UNAFFILIATED')
        assert not user.has_role(SystemRole.MEMBER)
        assert user.has_role(SystemRole.UNAFFILIATED)

    def test_none_role_defaults_to_unaffiliated(self):
        user = _make_user(None)
        assert user.has_role(SystemRole.UNAFFILIATED)
        assert not user.has_role(SystemRole.MEMBER)


# ============================================================
# User.set_role() Tests
# ============================================================

class TestSetRole:
    """Test User.set_role() with is_admin sync.

    Uses a simple namespace object to test set_role logic directly,
    since SQLAlchemy instrumented attributes don't work outside a session.
    """

    def _make_settable_user(self, role_name='MEMBER', is_admin=False):
        """Create an object that can receive set_role() assignments."""
        class FakeUser:
            pass
        user = FakeUser()
        user.role = role_name
        user.is_admin = is_admin
        # Bind set_role method from User class
        user.set_role = lambda new_role, sync_is_admin=True: User.set_role(user, new_role, sync_is_admin)
        return user

    def test_set_admin_syncs_is_admin_true(self):
        user = self._make_settable_user('MEMBER', is_admin=False)
        user.set_role(SystemRole.ADMIN)
        assert user.role == 'ADMIN'
        assert user.is_admin is True

    def test_set_member_syncs_is_admin_false(self):
        user = self._make_settable_user('ADMIN', is_admin=True)
        user.set_role(SystemRole.MEMBER)
        assert user.role == 'MEMBER'
        assert user.is_admin is False

    def test_set_office_manager_is_admin_false(self):
        user = self._make_settable_user('ADMIN', is_admin=True)
        user.set_role(SystemRole.OFFICE_MANAGER)
        assert user.role == 'OFFICE_MANAGER'
        assert user.is_admin is False

    def test_set_role_without_sync(self):
        user = self._make_settable_user('MEMBER', is_admin=False)
        user.set_role(SystemRole.ADMIN, sync_is_admin=False)
        assert user.role == 'ADMIN'
        assert user.is_admin is False  # Not synced


# ============================================================
# Permission Helper Methods Tests
# ============================================================

class TestPermissionHelpers:
    """Test can_access_admin_panel, can_manage_users, can_moderate_forum."""

    def test_admin_can_access_admin_panel(self):
        assert _make_user('ADMIN').can_access_admin_panel()

    def test_office_manager_can_access_admin_panel(self):
        assert _make_user('OFFICE_MANAGER').can_access_admin_panel()

    def test_manager_cannot_access_admin_panel(self):
        assert not _make_user('MANAGER').can_access_admin_panel()

    def test_member_cannot_access_admin_panel(self):
        assert not _make_user('MEMBER').can_access_admin_panel()

    def test_only_admin_can_manage_users(self):
        assert _make_user('ADMIN').can_manage_users()
        assert not _make_user('OFFICE_MANAGER').can_manage_users()
        assert not _make_user('MANAGER').can_manage_users()

    def test_office_manager_can_moderate_forum(self):
        assert _make_user('ADMIN').can_moderate_forum()
        assert _make_user('OFFICE_MANAGER').can_moderate_forum()
        assert not _make_user('MANAGER').can_moderate_forum()


# ============================================================
# CompanyRole Tests
# ============================================================

class TestCompanyRole:
    """Test CompanyRole enum and hierarchy."""

    def test_role_ordering(self):
        assert CompanyRole.NONE < CompanyRole.VIEWER
        assert CompanyRole.VIEWER < CompanyRole.EMPLOYEE
        assert CompanyRole.EMPLOYEE < CompanyRole.MANAGER

    def test_from_string(self):
        assert CompanyRole.from_string('MANAGER') == CompanyRole.MANAGER
        assert CompanyRole.from_string('EMPLOYEE') == CompanyRole.EMPLOYEE
        assert CompanyRole.from_string('INVALID') == CompanyRole.NONE
