"""
Unit tests for core middleware classes.

Tests use mocks to avoid database access - following the Testing Pyramid.
Tests cover:
- TenantHeaderMiddleware
- SandboxModeMiddleware
- MasqueradeAuditMiddleware
"""
from unittest.mock import Mock, patch, MagicMock, call
import pytest


class TestTenantHeaderMiddleware:
    """Test TenantHeaderMiddleware class."""

    def test_switches_tenant_based_on_header(self):
        """Should switch tenant when X-Business-Subdomain header is present."""
        from smoothschedule.identity.core.middleware import TenantHeaderMiddleware

        middleware = TenantHeaderMiddleware(get_response=Mock())

        # Mock the tenant lookup
        mock_tenant = Mock()
        mock_tenant.schema_name = 'demo'

        # Mock request
        request = Mock()
        request.META = {'HTTP_X_BUSINESS_SUBDOMAIN': 'demo'}
        request.tenant = Mock()
        request.tenant.schema_name = 'public'

        with patch('smoothschedule.identity.core.middleware.get_tenant_model') as mock_get_model:
            with patch('smoothschedule.identity.core.middleware.connection') as mock_conn:
                mock_tenant_model = Mock()
                mock_tenant_model.objects.get.return_value = mock_tenant
                mock_get_model.return_value = mock_tenant_model

                middleware.process_request(request)

                # Should switch tenant
                assert request.tenant == mock_tenant
                mock_conn.set_tenant.assert_called_once_with(mock_tenant)

    def test_does_not_switch_when_tenant_not_found_by_schema(self):
        """Should try domain lookup when schema_name lookup fails."""
        from smoothschedule.identity.core.middleware import TenantHeaderMiddleware

        middleware = TenantHeaderMiddleware(get_response=Mock())

        mock_domain = Mock()
        mock_domain.tenant = Mock(schema_name='demo')

        request = Mock()
        request.META = {'HTTP_X_BUSINESS_SUBDOMAIN': 'demo'}
        request.tenant = Mock()
        request.tenant.schema_name = 'public'

        with patch('smoothschedule.identity.core.middleware.get_tenant_model') as mock_get_model:
            with patch('smoothschedule.identity.core.middleware.connection') as mock_conn:
                with patch('django.apps.apps.get_model') as mock_apps_get_model:
                    mock_tenant_model = Mock()

                    # First lookup by schema_name fails
                    from django_tenants.utils import get_tenant_model
                    TenantClass = type('Tenant', (), {'DoesNotExist': Exception})
                    mock_tenant_model.DoesNotExist = TenantClass.DoesNotExist
                    mock_tenant_model.objects.get.side_effect = TenantClass.DoesNotExist()
                    mock_get_model.return_value = mock_tenant_model

                    # Domain lookup succeeds
                    mock_domain_model = Mock()
                    mock_domain_model.objects.filter.return_value.first.return_value = mock_domain
                    mock_apps_get_model.return_value = mock_domain_model

                    middleware.process_request(request)

                    # Should switch to domain's tenant
                    assert request.tenant == mock_domain.tenant
                    mock_conn.set_tenant.assert_called_once_with(mock_domain.tenant)

    def test_does_not_switch_when_no_header(self):
        """Should not switch tenant when header is missing."""
        from smoothschedule.identity.core.middleware import TenantHeaderMiddleware

        middleware = TenantHeaderMiddleware(get_response=Mock())

        request = Mock()
        request.META = {}
        original_tenant = Mock()
        request.tenant = original_tenant

        result = middleware.process_request(request)

        # Should not change tenant
        assert request.tenant == original_tenant
        assert result is None

    def test_does_not_switch_when_already_correct_tenant(self):
        """Should not switch when already on the correct tenant."""
        from smoothschedule.identity.core.middleware import TenantHeaderMiddleware

        middleware = TenantHeaderMiddleware(get_response=Mock())

        mock_tenant = Mock()
        mock_tenant.schema_name = 'demo'

        request = Mock()
        request.META = {'HTTP_X_BUSINESS_SUBDOMAIN': 'demo'}
        request.tenant = mock_tenant

        with patch('smoothschedule.identity.core.middleware.get_tenant_model') as mock_get_model:
            with patch('smoothschedule.identity.core.middleware.connection') as mock_conn:
                mock_tenant_model = Mock()
                mock_tenant_model.objects.get.return_value = mock_tenant
                mock_get_model.return_value = mock_tenant_model

                middleware.process_request(request)

                # Should not call set_tenant since we're already on correct schema
                mock_conn.set_tenant.assert_not_called()

    def test_handles_tenant_lookup_exception_gracefully(self):
        """Should handle exceptions during tenant lookup gracefully."""
        from smoothschedule.identity.core.middleware import TenantHeaderMiddleware

        middleware = TenantHeaderMiddleware(get_response=Mock())

        request = Mock()
        request.META = {'HTTP_X_BUSINESS_SUBDOMAIN': 'demo'}
        original_tenant = Mock()
        original_tenant.schema_name = 'public'
        request.tenant = original_tenant

        with patch('smoothschedule.identity.core.middleware.get_tenant_model') as mock_get_model:
            # Create a proper DoesNotExist exception
            TenantClass = type('Tenant', (), {'DoesNotExist': type('DoesNotExist', (Exception,), {})})
            mock_tenant_model = Mock()
            mock_tenant_model.DoesNotExist = TenantClass.DoesNotExist
            mock_tenant_model.objects.get.side_effect = TenantClass.DoesNotExist("Not found")
            mock_get_model.return_value = mock_tenant_model

            with patch('django.apps.apps.get_model') as mock_apps_get_model:
                # Domain lookup also fails
                mock_domain_model = Mock()
                mock_domain_model.objects.filter.return_value.first.return_value = None
                mock_apps_get_model.return_value = mock_domain_model

                result = middleware.process_request(request)

                # Should not crash and not change tenant
                assert request.tenant == original_tenant


class TestSandboxModeMiddleware:
    """Test SandboxModeMiddleware class."""

    def test_sets_sandbox_mode_false_by_default(self):
        """Should initialize sandbox_mode to False."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = False

        middleware.process_request(request)

        assert request.sandbox_mode is False

    def test_skips_sandbox_for_public_schema(self):
        """Should skip sandbox switching for public schema."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.tenant = Mock()
        request.tenant.schema_name = 'public'

        middleware.process_request(request)

        assert request.sandbox_mode is False

    def test_skips_sandbox_when_no_tenant(self):
        """Should skip sandbox switching when tenant is None."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.tenant = None

        middleware.process_request(request)

        assert request.sandbox_mode is False

    def test_skips_sandbox_when_not_enabled_for_tenant(self):
        """Should skip when tenant has sandbox_enabled=False."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = False

        middleware.process_request(request)

        assert request.sandbox_mode is False

    def test_skips_sandbox_when_no_sandbox_schema_name(self):
        """Should skip when sandbox_schema_name is not configured."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = True
        request.tenant.sandbox_schema_name = None

        middleware.process_request(request)

        assert request.sandbox_mode is False

    def test_detects_sandbox_mode_from_bearer_token_prefix(self):
        """Should detect sandbox mode from ss_test_ token prefix."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.META = {'HTTP_AUTHORIZATION': 'Bearer ss_test_1234567890abcdef'}
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = True
        request.tenant.sandbox_schema_name = 'demo_sandbox'

        with patch('smoothschedule.identity.core.middleware.connection') as mock_conn:
            middleware.process_request(request)

            assert request.sandbox_mode is True
            mock_conn.set_schema.assert_called_once_with('demo_sandbox')

    def test_detects_live_mode_from_bearer_token_prefix(self):
        """Should detect live mode from ss_live_ token prefix."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.META = {'HTTP_AUTHORIZATION': 'Bearer ss_live_1234567890abcdef'}
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = True
        request.tenant.sandbox_schema_name = 'demo_sandbox'

        middleware.process_request(request)

        assert request.sandbox_mode is False

    def test_detects_sandbox_mode_from_header(self):
        """Should detect sandbox mode from X-Sandbox-Mode header."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.META = {'HTTP_X_SANDBOX_MODE': 'true'}
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = True
        request.tenant.sandbox_schema_name = 'demo_sandbox'

        with patch('smoothschedule.identity.core.middleware.connection') as mock_conn:
            middleware.process_request(request)

            assert request.sandbox_mode is True
            mock_conn.set_schema.assert_called_once_with('demo_sandbox')

    def test_detects_live_mode_from_header(self):
        """Should detect live mode from X-Sandbox-Mode: false header."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.META = {'HTTP_X_SANDBOX_MODE': 'false'}
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = True
        request.tenant.sandbox_schema_name = 'demo_sandbox'

        middleware.process_request(request)

        assert request.sandbox_mode is False

    def test_detects_sandbox_mode_from_session(self):
        """Should detect sandbox mode from session variable."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.META = {}
        request.session = {'sandbox_mode': True}
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = True
        request.tenant.sandbox_schema_name = 'demo_sandbox'

        with patch('smoothschedule.identity.core.middleware.connection') as mock_conn:
            middleware.process_request(request)

            assert request.sandbox_mode is True
            mock_conn.set_schema.assert_called_once_with('demo_sandbox')

    def test_priority_order_token_over_header(self):
        """Should prioritize token prefix over header."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.META = {
            'HTTP_AUTHORIZATION': 'Bearer ss_live_1234567890',
            'HTTP_X_SANDBOX_MODE': 'true'
        }
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = True
        request.tenant.sandbox_schema_name = 'demo_sandbox'

        middleware.process_request(request)

        # Token says live, header says sandbox - token wins
        assert request.sandbox_mode is False

    def test_priority_order_header_over_session(self):
        """Should prioritize header over session."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.META = {'HTTP_X_SANDBOX_MODE': 'false'}
        request.session = {'sandbox_mode': True}
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = True
        request.tenant.sandbox_schema_name = 'demo_sandbox'

        middleware.process_request(request)

        # Header says false, session says true - header wins
        assert request.sandbox_mode is False

    def test_handles_set_schema_exception_gracefully(self):
        """Should fall back to live mode if set_schema fails."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.path = '/api/events/'
        request.META = {'HTTP_X_SANDBOX_MODE': 'true'}
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = True
        request.tenant.sandbox_schema_name = 'demo_sandbox'
        request.tenant.name = 'Demo Tenant'

        with patch('smoothschedule.identity.core.middleware.connection') as mock_conn:
            mock_conn.set_schema.side_effect = Exception("Schema does not exist")

            middleware.process_request(request)

            # Should fall back to live mode
            assert request.sandbox_mode is False

    def test_adds_sandbox_header_to_response(self):
        """Should add X-SmoothSchedule-Sandbox header to response."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.sandbox_mode = True

        response = Mock()
        response.__setitem__ = Mock()

        result = middleware.process_response(request, response)

        response.__setitem__.assert_called_once_with('X-SmoothSchedule-Sandbox', 'true')

    def test_does_not_add_header_when_not_sandbox_mode(self):
        """Should not add header when sandbox_mode is False."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock()
        request.sandbox_mode = False

        response = Mock()
        response.__setitem__ = Mock()

        result = middleware.process_response(request, response)

        response.__setitem__.assert_not_called()

    def test_handles_missing_session_gracefully(self):
        """Should handle requests without session attribute."""
        from smoothschedule.identity.core.middleware import SandboxModeMiddleware

        middleware = SandboxModeMiddleware(get_response=Mock())

        request = Mock(spec=['path', 'META', 'tenant'])
        request.path = '/api/events/'
        request.META = {}
        request.tenant = Mock()
        request.tenant.schema_name = 'demo'
        request.tenant.sandbox_enabled = True
        request.tenant.sandbox_schema_name = 'demo_sandbox'

        middleware.process_request(request)

        assert request.sandbox_mode is False


class TestMasqueradeAuditMiddleware:
    """Test MasqueradeAuditMiddleware class."""

    def test_initializes_masquerade_flags_for_authenticated_user(self):
        """Should initialize masquerade flags on request."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.session = {}

        middleware.process_request(request)

        assert request.is_masquerading is False
        assert request.actual_user is None
        assert request.masquerade_metadata == {}

    def test_initializes_masquerade_flags_for_unauthenticated_user(self):
        """Should initialize flags even for unauthenticated users."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = False
        request.session = {}

        middleware.process_request(request)

        assert request.is_masquerading is False
        assert request.actual_user is None

    def test_detects_masquerading_from_hijack_history(self):
        """Should detect masquerading when hijack_history exists."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        # Mock the actual admin user
        mock_admin = Mock()
        mock_admin.id = 1
        mock_admin.email = 'admin@example.com'
        mock_admin.role = 'SUPERUSER'

        # Mock the hijacked user
        mock_customer = Mock()
        mock_customer.id = 2
        mock_customer.email = 'customer@example.com'
        mock_customer.role = 'CUSTOMER'

        # Mock session as proper object with session_key attribute
        mock_session = Mock()
        mock_session.__getitem__ = lambda self, key: {'hijack_history': [1, 2]}.get(key)
        mock_session.get = lambda key, default=None: {'hijack_history': [1, 2]}.get(key, default)
        mock_session.session_key = 'test_session_key'

        request = Mock()
        request.user = mock_customer
        request.user.is_authenticated = True
        request.session = mock_session

        with patch('smoothschedule.identity.users.models.User') as mock_user_model:
            mock_user_model.objects.get.return_value = mock_admin

            middleware.process_request(request)

            assert request.is_masquerading is True
            assert request.actual_user == mock_admin
            assert request.masquerade_metadata['apparent_user_id'] == 2
            assert request.masquerade_metadata['apparent_user_email'] == 'customer@example.com'
            assert request.masquerade_metadata['actual_user_id'] == 1
            assert request.masquerade_metadata['actual_user_email'] == 'admin@example.com'

    def test_handles_empty_hijack_history(self):
        """Should not detect masquerading when hijack_history is empty."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.session = {'hijack_history': []}

        middleware.process_request(request)

        assert request.is_masquerading is False
        assert request.actual_user is None

    def test_handles_missing_hijack_history(self):
        """Should not detect masquerading when hijack_history missing."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.session = {}

        middleware.process_request(request)

        assert request.is_masquerading is False

    def test_handles_nonexistent_original_user(self):
        """Should handle case where original user was deleted."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.email = 'customer@example.com'
        request.session = {'hijack_history': [999]}

        with patch('smoothschedule.identity.users.models.User') as mock_user_model:
            # Simulate User.DoesNotExist
            mock_user_model.DoesNotExist = type('DoesNotExist', (Exception,), {})
            mock_user_model.objects.get.side_effect = mock_user_model.DoesNotExist()

            middleware.process_request(request)

            # Should clear corrupted session and set masquerading to False
            assert request.is_masquerading is False
            assert 'hijack_history' not in request.session

    def test_logs_masquerade_view_access(self):
        """Should log when masquerading user accesses a view."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        mock_admin = Mock()
        mock_admin.email = 'admin@example.com'
        mock_admin.get_role_display.return_value = 'Superuser'

        mock_customer = Mock()
        mock_customer.email = 'customer@example.com'
        mock_customer.get_role_display.return_value = 'Customer'
        mock_customer.tenant = Mock()
        mock_customer.tenant.name = 'Demo Business'

        request = Mock()
        request.path = '/api/customers/'
        request.method = 'GET'
        request.is_masquerading = True
        request.user = mock_customer
        request.actual_user = mock_admin
        request.META = {
            'REMOTE_ADDR': '192.168.1.1',
            'HTTP_USER_AGENT': 'Mozilla/5.0'
        }

        view_func = Mock()
        view_func.__name__ = 'CustomerViewSet'

        with patch('smoothschedule.identity.core.middleware.logger') as mock_logger:
            middleware.process_view(request, view_func, [], {})

            # Should log the masquerade access
            mock_logger.info.assert_called_once()
            log_message = mock_logger.info.call_args[0][0]
            assert 'admin@example.com' in log_message
            assert 'customer@example.com' in log_message

    def test_does_not_log_when_not_masquerading(self):
        """Should not log when user is not masquerading."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock()
        request.is_masquerading = False
        request.path = '/api/customers/'

        view_func = Mock()

        with patch('smoothschedule.identity.core.middleware.logger') as mock_logger:
            middleware.process_view(request, view_func, [], {})

            # Should not log
            mock_logger.info.assert_not_called()

    def test_skips_logging_for_admin_paths(self):
        """Should skip logging for admin interface access."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock()
        request.is_masquerading = True
        request.path = '/admin/auth/user/'

        view_func = Mock()

        with patch('smoothschedule.identity.core.middleware.logger') as mock_logger:
            middleware.process_view(request, view_func, [], {})

            # Should not log admin paths (too noisy)
            mock_logger.info.assert_not_called()

    def test_skips_logging_for_static_files(self):
        """Should skip logging for static/media files."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock()
        request.is_masquerading = True

        view_func = Mock()

        with patch('smoothschedule.identity.core.middleware.logger') as mock_logger:
            # Test static
            request.path = '/static/css/style.css'
            middleware.process_view(request, view_func, [], {})

            # Test media
            request.path = '/media/uploads/image.png'
            middleware.process_view(request, view_func, [], {})

            # Should not log
            mock_logger.info.assert_not_called()

    def test_adds_masquerading_header_to_response(self):
        """Should add X-SmoothSchedule-Masquerading header to response."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        mock_admin = Mock()
        mock_admin.email = 'admin@example.com'

        request = Mock()
        request.is_masquerading = True
        request.actual_user = mock_admin

        response = Mock()
        response.__setitem__ = Mock()

        middleware.process_response(request, response)

        # Should set both headers
        calls = response.__setitem__.call_args_list
        assert call('X-SmoothSchedule-Masquerading', 'true') in calls
        assert call('X-SmoothSchedule-Actual-User', 'admin@example.com') in calls

    def test_does_not_add_header_when_not_masquerading(self):
        """Should not add headers when not masquerading."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock()
        request.is_masquerading = False

        response = Mock()
        response.__setitem__ = Mock()

        middleware.process_response(request, response)

        response.__setitem__.assert_not_called()

    def test_handles_missing_user_attribute_gracefully(self):
        """Should handle requests without user attribute."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock(spec=['session'])
        request.session = {}

        # Should not crash
        middleware.process_request(request)

        assert request.is_masquerading is False

    def test_extracts_client_ip_from_x_forwarded_for(self):
        """Should extract client IP from X-Forwarded-For header."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock()
        request.META = {
            'HTTP_X_FORWARDED_FOR': '192.168.1.100, 10.0.0.1, 172.16.0.1',
            'REMOTE_ADDR': '127.0.0.1'
        }

        ip = middleware._get_client_ip(request)

        # Should take the first IP (client IP, before proxies)
        assert ip == '192.168.1.100'

    def test_extracts_client_ip_from_remote_addr_when_no_forwarded(self):
        """Should fallback to REMOTE_ADDR when no X-Forwarded-For."""
        from smoothschedule.identity.core.middleware import MasqueradeAuditMiddleware

        middleware = MasqueradeAuditMiddleware(get_response=Mock())

        request = Mock()
        request.META = {'REMOTE_ADDR': '192.168.1.50'}

        ip = middleware._get_client_ip(request)

        assert ip == '192.168.1.50'


class TestMasqueradeEventLogger:
    """Test MasqueradeEventLogger utility class."""

    def test_logs_hijack_start_event(self):
        """Should log hijack start with structured data."""
        from smoothschedule.identity.core.middleware import MasqueradeEventLogger

        hijacker = Mock()
        hijacker.id = 1
        hijacker.email = 'admin@example.com'
        hijacker.get_role_display.return_value = 'Superuser'

        hijacked = Mock()
        hijacked.id = 2
        hijacked.email = 'customer@example.com'
        hijacked.get_role_display.return_value = 'Customer'

        request = Mock()
        request.META = {
            'REMOTE_ADDR': '192.168.1.1',
            'HTTP_USER_AGENT': 'Mozilla/5.0'
        }
        request.session = Mock()
        request.session.session_key = 'test_session_key'

        with patch('smoothschedule.identity.core.middleware.logger') as mock_logger:
            MasqueradeEventLogger.log_hijack_start(hijacker, hijacked, request)

            # Should log as warning level
            mock_logger.warning.assert_called_once()
            log_message = mock_logger.warning.call_args[0][0]
            assert 'HIJACK START' in log_message
            assert 'admin@example.com' in log_message
            assert 'customer@example.com' in log_message

            # Check structured data
            log_extra = mock_logger.warning.call_args[1]['extra']
            assert 'audit_data' in log_extra
            audit_data = log_extra['audit_data']
            assert audit_data['action'] == 'HIJACK_START'
            assert audit_data['hijacker_email'] == 'admin@example.com'
            assert audit_data['hijacked_email'] == 'customer@example.com'

    def test_logs_hijack_end_event(self):
        """Should log hijack end with duration."""
        from smoothschedule.identity.core.middleware import MasqueradeEventLogger

        hijacker = Mock()
        hijacker.id = 1
        hijacker.email = 'admin@example.com'

        hijacked = Mock()
        hijacked.id = 2
        hijacked.email = 'customer@example.com'

        request = Mock()
        request.META = {'REMOTE_ADDR': '192.168.1.1'}
        request.session = Mock()
        request.session.session_key = 'test_session_key'

        with patch('smoothschedule.identity.core.middleware.logger') as mock_logger:
            MasqueradeEventLogger.log_hijack_end(
                hijacker, hijacked, request, duration_seconds=300
            )

            # Should log as warning level
            mock_logger.warning.assert_called_once()
            log_message = mock_logger.warning.call_args[0][0]
            assert 'HIJACK END' in log_message

            # Check duration in audit data
            log_extra = mock_logger.warning.call_args[1]['extra']
            audit_data = log_extra['audit_data']
            assert audit_data['action'] == 'HIJACK_END'
            assert audit_data['duration_seconds'] == 300

    def test_logs_hijack_denied_event(self):
        """Should log denied hijack attempts."""
        from smoothschedule.identity.core.middleware import MasqueradeEventLogger

        hijacker = Mock()
        hijacker.id = 1
        hijacker.email = 'staff@example.com'
        hijacker.get_role_display.return_value = 'Staff'

        hijacked = Mock()
        hijacked.id = 2
        hijacked.email = 'owner@example.com'
        hijacked.get_role_display.return_value = 'Owner'

        request = Mock()
        request.META = {
            'REMOTE_ADDR': '192.168.1.1',
            'HTTP_USER_AGENT': 'Mozilla/5.0'
        }

        with patch('smoothschedule.identity.core.middleware.logger') as mock_logger:
            MasqueradeEventLogger.log_hijack_denied(
                hijacker, hijacked, request, reason='Insufficient permissions'
            )

            # Should log as error level
            mock_logger.error.assert_called_once()
            log_message = mock_logger.error.call_args[0][0]
            assert 'HIJACK DENIED' in log_message
            assert 'Insufficient permissions' in log_message

            # Check audit data
            log_extra = mock_logger.error.call_args[1]['extra']
            audit_data = log_extra['audit_data']
            assert audit_data['action'] == 'HIJACK_DENIED'
            assert audit_data['denial_reason'] == 'Insufficient permissions'
