"""
Unit tests for core mixins (permission classes and queryset mixins).

Tests use mocks only - no database access required for fast execution.
"""
from unittest.mock import Mock, patch, PropertyMock, MagicMock
import pytest

from smoothschedule.identity.core.mixins import (
    _staff_has_permission_override,
    DenyStaffWritePermission,
    DenyStaffAllAccessPermission,
    DenyStaffListPermission,
    TenantFilteredQuerySetMixin,
    SandboxFilteredQuerySetMixin,
    UserTenantFilteredMixin,
    AutomationFeatureRequiredMixin,
    TaskFeatureRequiredMixin,
    StandardResponseMixin,
    TenantAPIView,
    TenantRequiredAPIView,
)
from rest_framework.exceptions import PermissionDenied


# ==============================================================================
# Helper Function Tests
# ==============================================================================

class TestStaffHasPermissionOverride:
    """Test the _staff_has_permission_override helper function."""

    def test_returns_true_when_permission_exists(self):
        user = Mock()
        user.is_authenticated = True
        user.permissions = {'can_access_resources': True}

        result = _staff_has_permission_override(user, 'can_access_resources')

        assert result is True

    def test_returns_false_when_permission_does_not_exist(self):
        user = Mock()
        user.is_authenticated = True
        user.permissions = {'can_access_resources': False}

        result = _staff_has_permission_override(user, 'can_access_resources')

        assert result is False

    def test_returns_false_when_permission_key_not_in_dict(self):
        user = Mock()
        user.is_authenticated = True
        user.permissions = {}

        result = _staff_has_permission_override(user, 'can_access_resources')

        assert result is False

    def test_returns_false_when_user_not_authenticated(self):
        user = Mock()
        user.is_authenticated = False

        result = _staff_has_permission_override(user, 'can_access_resources')

        assert result is False

    def test_returns_false_when_permissions_is_none(self):
        user = Mock()
        user.is_authenticated = True
        user.permissions = None

        result = _staff_has_permission_override(user, 'can_access_resources')

        assert result is False

    def test_handles_missing_permissions_attribute(self):
        user = Mock(spec=['is_authenticated'])
        user.is_authenticated = True

        result = _staff_has_permission_override(user, 'can_access_resources')

        assert result is False


# ==============================================================================
# DenyStaffWritePermission Tests
# ==============================================================================

class TestDenyStaffWritePermission:
    """Test the DenyStaffWritePermission class."""

    def test_allows_read_operations_for_all_users(self):
        permission = DenyStaffWritePermission()
        request = Mock()
        request.method = 'GET'
        view = Mock()

        result = permission.has_permission(request, view)

        assert result is True

    def test_allows_head_operations_for_all_users(self):
        permission = DenyStaffWritePermission()
        request = Mock()
        request.method = 'HEAD'
        view = Mock()

        result = permission.has_permission(request, view)

        assert result is True

    def test_allows_options_operations_for_all_users(self):
        permission = DenyStaffWritePermission()
        request = Mock()
        request.method = 'OPTIONS'
        view = Mock()

        result = permission.has_permission(request, view)

        assert result is True

    @patch('smoothschedule.identity.users.models.User')
    def test_denies_staff_write_operations(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffWritePermission()
        request = Mock()
        request.method = 'POST'
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'
        request.user.permissions = {}

        view = Mock()
        view.basename = 'resources'

        result = permission.has_permission(request, view)

        assert result is False

    @patch('smoothschedule.identity.users.models.User')
    def test_allows_non_staff_write_operations(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffWritePermission()
        request = Mock()
        request.method = 'POST'
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_OWNER'

        view = Mock()

        result = permission.has_permission(request, view)

        assert result is True

    @patch('smoothschedule.identity.users.models.User')
    @patch('smoothschedule.identity.core.mixins._staff_has_permission_override')
    def test_allows_staff_with_permission_override(self, mock_permission_check, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'
        mock_permission_check.return_value = True

        permission = DenyStaffWritePermission()
        request = Mock()
        request.method = 'POST'
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'

        view = Mock()
        view.basename = 'resources'

        result = permission.has_permission(request, view)

        assert result is True
        mock_permission_check.assert_called_once()

    def test_allows_unauthenticated_write(self):
        # Permission check happens, but DRF authentication will handle it
        permission = DenyStaffWritePermission()
        request = Mock()
        request.method = 'POST'
        request.user = Mock()
        request.user.is_authenticated = False

        view = Mock()

        result = permission.has_permission(request, view)

        assert result is True

    @patch('smoothschedule.identity.users.models.User')
    @patch('smoothschedule.identity.core.mixins._staff_has_permission_override')
    def test_uses_custom_permission_key_when_provided(self, mock_permission_check, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'
        mock_permission_check.return_value = True

        permission = DenyStaffWritePermission()
        request = Mock()
        request.method = 'POST'
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'

        view = Mock()
        view.staff_write_permission_key = 'can_edit_resources'

        result = permission.has_permission(request, view)

        assert result is True
        # Verify it was called with the custom key
        call_args = mock_permission_check.call_args
        assert call_args[0][1] == 'can_edit_resources'

    @patch('smoothschedule.identity.users.models.User')
    def test_derives_permission_from_basename(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffWritePermission()
        request = Mock()
        request.method = 'PUT'
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'
        request.user.permissions = {}

        view = Mock()
        view.basename = 'services'

        result = permission.has_permission(request, view)

        # Should deny because permission is can_write_services
        assert result is False

    @patch('smoothschedule.identity.users.models.User')
    def test_derives_permission_from_queryset_model(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffWritePermission()
        request = Mock()
        request.method = 'DELETE'
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'
        request.user.permissions = {}

        mock_model = Mock()
        mock_model._meta.model_name = 'event'

        mock_queryset = Mock()
        mock_queryset.model = mock_model

        view = Mock()
        view.basename = None
        view.queryset = mock_queryset

        result = permission.has_permission(request, view)

        # Should deny because permission is can_write_events
        assert result is False


# ==============================================================================
# DenyStaffAllAccessPermission Tests
# ==============================================================================

class TestDenyStaffAllAccessPermission:
    """Test the DenyStaffAllAccessPermission class."""

    @patch('smoothschedule.identity.users.models.User')
    def test_denies_staff_all_operations(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffAllAccessPermission()
        request = Mock()
        request.method = 'GET'
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'
        request.user.permissions = {}

        view = Mock()
        view.basename = 'resources'

        result = permission.has_permission(request, view)

        assert result is False

    @patch('smoothschedule.identity.users.models.User')
    def test_allows_non_staff_all_operations(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffAllAccessPermission()
        request = Mock()
        request.method = 'GET'
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_MANAGER'

        view = Mock()

        result = permission.has_permission(request, view)

        assert result is True

    @patch('smoothschedule.identity.users.models.User')
    @patch('smoothschedule.identity.core.mixins._staff_has_permission_override')
    def test_allows_staff_with_permission_override(self, mock_permission_check, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'
        mock_permission_check.return_value = True

        permission = DenyStaffAllAccessPermission()
        request = Mock()
        request.method = 'GET'
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'

        view = Mock()
        view.basename = 'services'

        result = permission.has_permission(request, view)

        assert result is True

    @patch('smoothschedule.identity.users.models.User')
    @patch('smoothschedule.identity.core.mixins._staff_has_permission_override')
    def test_uses_custom_permission_key(self, mock_permission_check, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'
        mock_permission_check.return_value = True

        permission = DenyStaffAllAccessPermission()
        request = Mock()
        request.method = 'GET'
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'

        view = Mock()
        view.staff_access_permission_key = 'can_manage_equipment'

        result = permission.has_permission(request, view)

        assert result is True

    @patch('smoothschedule.identity.users.models.User')
    def test_denies_staff_write_operation(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffAllAccessPermission()
        request = Mock()
        request.method = 'POST'
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'
        request.user.permissions = {}

        view = Mock()
        view.basename = 'resources'

        result = permission.has_permission(request, view)

        assert result is False


# ==============================================================================
# DenyStaffListPermission Tests
# ==============================================================================

class TestDenyStaffListPermission:
    """Test the DenyStaffListPermission class."""

    def test_allows_staff_retrieve_action(self):
        permission = DenyStaffListPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'

        view = Mock()
        view.action = 'retrieve'

        result = permission.has_permission(request, view)

        assert result is True

    @patch('smoothschedule.identity.users.models.User')
    def test_denies_staff_list_action(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffListPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'
        request.user.permissions = {}

        view = Mock()
        view.action = 'list'
        view.basename = 'customers'

        result = permission.has_permission(request, view)

        assert result is False

    @patch('smoothschedule.identity.users.models.User')
    def test_denies_staff_create_action(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffListPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'
        request.user.permissions = {}

        view = Mock()
        view.action = 'create'
        view.basename = 'customers'

        result = permission.has_permission(request, view)

        assert result is False

    @patch('smoothschedule.identity.users.models.User')
    def test_denies_staff_update_action(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffListPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'
        request.user.permissions = {}

        view = Mock()
        view.action = 'update'
        view.basename = 'customers'

        result = permission.has_permission(request, view)

        assert result is False

    @patch('smoothschedule.identity.users.models.User')
    def test_denies_staff_partial_update_action(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffListPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'
        request.user.permissions = {}

        view = Mock()
        view.action = 'partial_update'
        view.basename = 'customers'

        result = permission.has_permission(request, view)

        assert result is False

    @patch('smoothschedule.identity.users.models.User')
    def test_denies_staff_destroy_action(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffListPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'
        request.user.permissions = {}

        view = Mock()
        view.action = 'destroy'
        view.basename = 'customers'

        result = permission.has_permission(request, view)

        assert result is False

    @patch('smoothschedule.identity.users.models.User')
    @patch('smoothschedule.identity.core.mixins._staff_has_permission_override')
    def test_allows_staff_list_with_list_permission_override(self, mock_permission_check, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'
        # First call for access permission returns False, second call for list permission returns True
        mock_permission_check.side_effect = [False, True]

        permission = DenyStaffListPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'

        view = Mock()
        view.action = 'list'
        view.basename = 'customers'

        result = permission.has_permission(request, view)

        assert result is True

    @patch('smoothschedule.identity.users.models.User')
    @patch('smoothschedule.identity.core.mixins._staff_has_permission_override')
    def test_allows_staff_list_with_access_permission_override(self, mock_permission_check, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'
        # First call for access permission returns True
        mock_permission_check.return_value = True

        permission = DenyStaffListPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'

        view = Mock()
        view.action = 'list'
        view.basename = 'customers'

        result = permission.has_permission(request, view)

        assert result is True

    @patch('smoothschedule.identity.users.models.User')
    @patch('smoothschedule.identity.core.mixins._staff_has_permission_override')
    def test_allows_staff_create_with_access_permission_override(self, mock_permission_check, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'
        mock_permission_check.return_value = True

        permission = DenyStaffListPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'

        view = Mock()
        view.action = 'create'
        view.basename = 'customers'

        result = permission.has_permission(request, view)

        assert result is True

    @patch('smoothschedule.identity.users.models.User')
    @patch('smoothschedule.identity.core.mixins._staff_has_permission_override')
    def test_denies_staff_create_with_only_list_permission(self, mock_permission_check, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'
        mock_permission_check.return_value = False

        permission = DenyStaffListPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_STAFF'

        view = Mock()
        view.action = 'create'
        view.basename = 'customers'

        result = permission.has_permission(request, view)

        assert result is False

    @patch('smoothschedule.identity.users.models.User')
    def test_allows_non_staff_all_actions(self, mock_user_class):
        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        permission = DenyStaffListPermission()
        request = Mock()
        request.user = Mock()
        request.user.is_authenticated = True
        request.user.role = 'TENANT_OWNER'

        view = Mock()
        view.action = 'list'

        result = permission.has_permission(request, view)

        assert result is True


# ==============================================================================
# TenantFilteredQuerySetMixin Tests
# ==============================================================================

class TestTenantFilteredQuerySetMixin:
    """Test the TenantFilteredQuerySetMixin class."""

    def test_returns_empty_queryset_for_unauthenticated_users(self):
        from rest_framework.viewsets import ModelViewSet

        class ConcreteViewSet(TenantFilteredQuerySetMixin, ModelViewSet):
            pass

        mock_base_queryset = Mock()
        mock_base_queryset.none.return_value = 'empty_queryset'

        viewset = ConcreteViewSet()
        viewset.queryset = mock_base_queryset
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.is_authenticated = False

        result = viewset.get_queryset()

        mock_base_queryset.none.assert_called_once()
        assert result == 'empty_queryset'

    @patch('smoothschedule.identity.users.models.User')
    def test_returns_empty_queryset_for_staff_when_deny_staff_queryset_true(self, mock_user_class):
        from rest_framework.viewsets import ModelViewSet

        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        class ConcreteViewSet(TenantFilteredQuerySetMixin, ModelViewSet):
            deny_staff_queryset = True

        mock_base_queryset = Mock()
        mock_base_queryset.none.return_value = 'empty_queryset'

        viewset = ConcreteViewSet()
        viewset.queryset = mock_base_queryset
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.is_authenticated = True
        viewset.request.user.role = 'TENANT_STAFF'

        result = viewset.get_queryset()

        mock_base_queryset.none.assert_called_once()
        assert result == 'empty_queryset'

    @patch('smoothschedule.identity.users.models.User')
    def test_returns_queryset_for_staff_when_deny_staff_queryset_false(self, mock_user_class):
        from rest_framework.viewsets import ModelViewSet

        mock_user_class.Role.TENANT_STAFF = 'TENANT_STAFF'

        class ConcreteViewSet(TenantFilteredQuerySetMixin, ModelViewSet):
            deny_staff_queryset = False

        mock_base_queryset = Mock()

        viewset = ConcreteViewSet()
        viewset.queryset = mock_base_queryset
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.is_authenticated = True
        viewset.request.user.role = 'TENANT_STAFF'
        viewset.request.user.tenant = Mock()
        viewset.request.user.tenant.schema_name = 'test_tenant'
        viewset.request.tenant = Mock()
        viewset.request.tenant.schema_name = 'test_tenant'

        result = viewset.get_queryset()

        assert result == mock_base_queryset

    def test_returns_empty_queryset_when_user_tenant_mismatch(self):
        from rest_framework.viewsets import ModelViewSet

        class ConcreteViewSet(TenantFilteredQuerySetMixin, ModelViewSet):
            pass

        mock_base_queryset = Mock()
        mock_base_queryset.none.return_value = 'empty_queryset'

        viewset = ConcreteViewSet()
        viewset.queryset = mock_base_queryset
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.is_authenticated = True
        viewset.request.user.tenant = Mock()
        viewset.request.user.tenant.schema_name = 'tenant_a'
        viewset.request.tenant = Mock()
        viewset.request.tenant.schema_name = 'tenant_b'

        result = viewset.get_queryset()

        mock_base_queryset.none.assert_called_once()
        assert result == 'empty_queryset'

    def test_calls_filter_queryset_for_tenant(self):
        from rest_framework.viewsets import ModelViewSet

        class ConcreteViewSet(TenantFilteredQuerySetMixin, ModelViewSet):
            def filter_queryset_for_tenant(self, queryset):
                return 'filtered_queryset'

        mock_base_queryset = Mock()

        viewset = ConcreteViewSet()
        viewset.queryset = mock_base_queryset
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.is_authenticated = True
        viewset.request.user.tenant = Mock()
        viewset.request.user.tenant.schema_name = 'test_tenant'
        viewset.request.tenant = Mock()
        viewset.request.tenant.schema_name = 'test_tenant'

        result = viewset.get_queryset()

        assert result == 'filtered_queryset'

    def test_handles_missing_tenant_on_request(self):
        from rest_framework.viewsets import ModelViewSet

        class ConcreteViewSet(TenantFilteredQuerySetMixin, ModelViewSet):
            pass

        mock_base_queryset = Mock()

        viewset = ConcreteViewSet()
        viewset.queryset = mock_base_queryset
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.is_authenticated = True
        viewset.request.user.tenant = None
        viewset.request.tenant = None

        result = viewset.get_queryset()

        assert result == mock_base_queryset


# ==============================================================================
# SandboxFilteredQuerySetMixin Tests
# ==============================================================================

class TestSandboxFilteredQuerySetMixin:
    """Test the SandboxFilteredQuerySetMixin class."""

    def test_filters_by_sandbox_mode_when_model_has_is_sandbox(self):
        from rest_framework.viewsets import ModelViewSet

        class ConcreteViewSet(SandboxFilteredQuerySetMixin, ModelViewSet):
            pass

        mock_model = Mock()
        # Mock hasattr check
        type(mock_model).is_sandbox = PropertyMock(return_value=True)

        mock_base_queryset = Mock()
        mock_base_queryset.model = mock_model
        mock_base_queryset.filter.return_value = 'filtered_queryset'

        viewset = ConcreteViewSet()
        viewset.queryset = mock_base_queryset
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.is_authenticated = True
        viewset.request.user.tenant = Mock()
        viewset.request.user.tenant.schema_name = 'test_tenant'
        viewset.request.tenant = Mock()
        viewset.request.tenant.schema_name = 'test_tenant'
        viewset.request.sandbox_mode = True

        result = viewset.get_queryset()

        mock_base_queryset.filter.assert_called_once_with(is_sandbox=True)

    def test_does_not_filter_when_model_lacks_is_sandbox(self):
        from rest_framework.viewsets import ModelViewSet

        class ConcreteViewSet(SandboxFilteredQuerySetMixin, ModelViewSet):
            pass

        mock_model = Mock(spec=[])  # No is_sandbox attribute

        mock_base_queryset = Mock()
        mock_base_queryset.model = mock_model

        viewset = ConcreteViewSet()
        viewset.queryset = mock_base_queryset
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.is_authenticated = True
        viewset.request.user.tenant = Mock()
        viewset.request.user.tenant.schema_name = 'test_tenant'
        viewset.request.tenant = Mock()
        viewset.request.tenant.schema_name = 'test_tenant'
        viewset.request.sandbox_mode = True

        result = viewset.get_queryset()

        assert result == mock_base_queryset


# ==============================================================================
# UserTenantFilteredMixin Tests
# ==============================================================================

class TestUserTenantFilteredMixin:
    """Test the UserTenantFilteredMixin class."""

    def test_filters_by_user_tenant(self):
        from rest_framework.viewsets import ModelViewSet

        class ConcreteViewSet(UserTenantFilteredMixin, ModelViewSet):
            pass

        mock_tenant = Mock()
        mock_model = Mock(spec=[])  # No is_sandbox

        mock_base_queryset = Mock()
        mock_base_queryset.model = mock_model
        mock_base_queryset.filter.return_value = 'filtered_queryset'

        viewset = ConcreteViewSet()
        viewset.queryset = mock_base_queryset
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.is_authenticated = True
        viewset.request.user.tenant = mock_tenant
        viewset.request.tenant = mock_tenant
        viewset.request.tenant.schema_name = 'test'

        result = viewset.get_queryset()

        mock_base_queryset.filter.assert_called_with(tenant=mock_tenant)

    def test_returns_empty_queryset_when_user_has_no_tenant(self):
        from rest_framework.viewsets import ModelViewSet

        class ConcreteViewSet(UserTenantFilteredMixin, ModelViewSet):
            pass

        mock_model = Mock(spec=[])  # No is_sandbox

        mock_base_queryset = Mock()
        mock_base_queryset.model = mock_model
        mock_base_queryset.none.return_value = 'empty_queryset'

        viewset = ConcreteViewSet()
        viewset.queryset = mock_base_queryset
        viewset.request = Mock()
        viewset.request.user = Mock()
        viewset.request.user.is_authenticated = True
        viewset.request.user.tenant = None
        viewset.request.tenant = None

        result = viewset.get_queryset()

        mock_base_queryset.none.assert_called_once()
        assert result == 'empty_queryset'


# ==============================================================================
# AutomationFeatureRequiredMixin Tests
# ==============================================================================

class TestAutomationFeatureRequiredMixin:
    """Test the AutomationFeatureRequiredMixin class."""

    def test_allows_access_when_tenant_has_automation_feature(self):
        viewset = AutomationFeatureRequiredMixin()
        viewset.request = Mock()
        viewset.request.tenant = Mock()
        viewset.request.tenant.has_feature.return_value = True

        # Should not raise
        viewset.check_automation_permission()
        viewset.request.tenant.has_feature.assert_called_once_with('can_use_automations')

    def test_denies_access_when_tenant_lacks_automation_feature(self):
        viewset = AutomationFeatureRequiredMixin()
        viewset.request = Mock()
        viewset.request.tenant = Mock()
        viewset.request.tenant.has_feature.return_value = False

        with pytest.raises(PermissionDenied) as exc_info:
            viewset.check_automation_permission()

        assert 'Automation access' in str(exc_info.value)

    def test_list_checks_automation_permission(self):
        viewset = AutomationFeatureRequiredMixin()
        viewset.request = Mock()
        viewset.request.tenant = Mock()
        viewset.request.tenant.has_feature.return_value = False

        with pytest.raises(PermissionDenied):
            viewset.list(viewset.request)

    def test_retrieve_checks_automation_permission(self):
        viewset = AutomationFeatureRequiredMixin()
        viewset.request = Mock()
        viewset.request.tenant = Mock()
        viewset.request.tenant.has_feature.return_value = False

        with pytest.raises(PermissionDenied):
            viewset.retrieve(viewset.request)

    def test_create_checks_automation_permission(self):
        viewset = AutomationFeatureRequiredMixin()
        viewset.request = Mock()
        viewset.request.tenant = Mock()
        viewset.request.tenant.has_feature.return_value = False

        with pytest.raises(PermissionDenied):
            viewset.create(viewset.request)


# ==============================================================================
# TaskFeatureRequiredMixin Tests
# ==============================================================================

class TestTaskFeatureRequiredMixin:
    """Test the TaskFeatureRequiredMixin class."""

    def test_allows_access_when_tenant_has_both_features(self):
        viewset = TaskFeatureRequiredMixin()
        viewset.request = Mock()
        viewset.request.tenant = Mock()
        viewset.request.tenant.has_feature.return_value = True

        # Should not raise
        viewset.check_automation_permission()

        # Should be called twice: once for automations, once for tasks
        assert viewset.request.tenant.has_feature.call_count == 2

    def test_denies_access_when_tenant_lacks_automation_feature(self):
        viewset = TaskFeatureRequiredMixin()
        viewset.request = Mock()
        viewset.request.tenant = Mock()
        viewset.request.tenant.has_feature.return_value = False

        with pytest.raises(PermissionDenied) as exc_info:
            viewset.check_automation_permission()

        assert 'Automation access' in str(exc_info.value)

    def test_denies_access_when_tenant_lacks_task_feature(self):
        viewset = TaskFeatureRequiredMixin()
        viewset.request = Mock()
        viewset.request.tenant = Mock()

        # Return True for automations, False for tasks
        def has_feature_side_effect(key):
            return key == 'can_use_automations'

        viewset.request.tenant.has_feature.side_effect = has_feature_side_effect

        with pytest.raises(PermissionDenied) as exc_info:
            viewset.check_automation_permission()

        assert 'Scheduled Tasks' in str(exc_info.value)


# ==============================================================================
# StandardResponseMixin Tests
# ==============================================================================

class TestStandardResponseMixin:
    """Test the StandardResponseMixin class."""

    @patch('rest_framework.response.Response')
    def test_success_response_returns_message_with_200(self, mock_response_class):
        mixin = StandardResponseMixin()

        result = mixin.success_response('Success!')

        mock_response_class.assert_called_once()
        call_args = mock_response_class.call_args
        assert call_args[0][0] == {'message': 'Success!'}

    @patch('rest_framework.response.Response')
    def test_success_response_includes_data(self, mock_response_class):
        mixin = StandardResponseMixin()

        result = mixin.success_response('Success!', data={'id': 1})

        call_args = mock_response_class.call_args
        assert call_args[0][0] == {'message': 'Success!', 'id': 1}

    @patch('rest_framework.response.Response')
    def test_error_response_returns_error_with_400(self, mock_response_class):
        mixin = StandardResponseMixin()

        result = mixin.error_response('Error!')

        mock_response_class.assert_called_once()
        call_args = mock_response_class.call_args
        assert call_args[0][0] == {'error': 'Error!'}


# ==============================================================================
# TenantAPIView Tests
# ==============================================================================

class TestTenantAPIView:
    """Test the TenantAPIView class."""

    def test_get_tenant_returns_tenant_from_request(self):
        view = TenantAPIView()
        view.request = Mock()
        view.request.tenant = 'test_tenant'

        result = view.get_tenant()

        assert result == 'test_tenant'

    def test_get_tenant_returns_none_when_no_tenant(self):
        view = TenantAPIView()
        view.request = Mock()
        view.request.tenant = None

        result = view.get_tenant()

        assert result is None

    def test_get_tenant_or_error_returns_tenant_when_exists(self):
        view = TenantAPIView()
        view.request = Mock()
        view.request.tenant = 'test_tenant'

        tenant, error = view.get_tenant_or_error()

        assert tenant == 'test_tenant'
        assert error is None

    def test_get_tenant_or_error_returns_error_when_missing(self):
        view = TenantAPIView()
        view.request = Mock()
        view.request.tenant = None

        with patch.object(view, 'tenant_required_response', return_value='error_response'):
            tenant, error = view.get_tenant_or_error()

        assert tenant is None
        assert error == 'error_response'

    def test_check_feature_returns_none_when_tenant_has_feature(self):
        view = TenantAPIView()
        view.request = Mock()
        view.request.tenant = Mock()
        view.request.tenant.has_feature.return_value = True

        result = view.check_feature('can_accept_payments')

        assert result is None

    def test_check_feature_returns_error_when_tenant_lacks_feature(self):
        view = TenantAPIView()
        view.request = Mock()
        view.request.tenant = Mock()
        view.request.tenant.has_feature.return_value = False

        with patch.object(view, 'error_response', return_value='error_response') as mock_error:
            result = view.check_feature('can_accept_payments')

            assert result == 'error_response'
            mock_error.assert_called_once()

    def test_check_feature_uses_custom_feature_name(self):
        view = TenantAPIView()
        view.request = Mock()
        view.request.tenant = Mock()
        view.request.tenant.has_feature.return_value = False

        with patch.object(view, 'error_response', return_value='error') as mock_error:
            view.check_feature('can_accept_payments', 'Payment Processing')

            call_args = mock_error.call_args[0][0]
            assert 'Payment Processing' in call_args


# ==============================================================================
# TenantRequiredAPIView Tests
# ==============================================================================

class TestTenantRequiredAPIView:
    """Test the TenantRequiredAPIView class."""

    def test_dispatch_sets_tenant_and_continues(self):
        from rest_framework.views import APIView

        class ConcreteView(TenantRequiredAPIView, APIView):
            pass

        view = ConcreteView()
        request = Mock()
        request.tenant = 'test_tenant'

        # Mock parent dispatch
        with patch.object(APIView, 'dispatch', return_value='response'):
            result = view.dispatch(request)

        assert view.tenant == 'test_tenant'
        assert result == 'response'

    def test_dispatch_returns_error_when_no_tenant(self):
        from rest_framework.views import APIView

        class ConcreteView(TenantRequiredAPIView, APIView):
            pass

        view = ConcreteView()
        request = Mock()
        request.tenant = None

        with patch.object(view, 'tenant_required_response', return_value='error_response'):
            result = view.dispatch(request)

        assert result == 'error_response'


# ==============================================================================
# Permission Key Derivation Tests
# ==============================================================================

class TestDenyStaffWritePermissionKeyDerivation:
    """Test _get_permission_key fallback paths in DenyStaffWritePermission."""

    def test_derives_key_from_basename(self):
        """Should derive permission key from view basename."""
        permission = DenyStaffWritePermission()
        view = Mock(spec=['basename'])
        view.basename = 'customers'

        result = permission._get_permission_key(view, 'write')

        assert result == 'can_write_customers'

    def test_derives_key_from_model_name(self):
        """Should derive permission key from queryset model name."""
        permission = DenyStaffWritePermission()
        view = Mock(spec=['queryset'])
        view.queryset = Mock()
        view.queryset.model._meta.model_name = 'customer'

        result = permission._get_permission_key(view, 'write')

        assert result == 'can_write_customers'

    def test_returns_default_when_no_basename_or_queryset(self):
        """Should return default key when no basename or queryset."""
        permission = DenyStaffWritePermission()
        view = Mock(spec=[])  # No basename or queryset

        result = permission._get_permission_key(view, 'write')

        assert result == 'can_write_resource'


class TestDenyStaffAllAccessPermissionKeyDerivation:
    """Test _get_permission_key fallback paths in DenyStaffAllAccessPermission."""

    def test_derives_key_from_basename(self):
        """Should derive permission key from view basename."""
        permission = DenyStaffAllAccessPermission()
        view = Mock(spec=['basename'])
        view.basename = 'services'

        result = permission._get_permission_key(view)

        assert result == 'can_access_services'

    def test_derives_key_from_model_name(self):
        """Should derive permission key from queryset model name."""
        permission = DenyStaffAllAccessPermission()
        view = Mock(spec=['queryset'])
        view.queryset = Mock()
        view.queryset.model._meta.model_name = 'service'

        result = permission._get_permission_key(view)

        assert result == 'can_access_services'

    def test_returns_default_when_no_basename_or_queryset(self):
        """Should return default key when no basename or queryset."""
        permission = DenyStaffAllAccessPermission()
        view = Mock(spec=[])  # No basename or queryset

        result = permission._get_permission_key(view)

        assert result == 'can_access_resource'


class TestDenyStaffListPermissionKeyDerivation:
    """Test _get_permission_key fallback paths in DenyStaffListPermission."""

    def test_derives_list_key_from_basename(self):
        """Should derive list permission key from view basename."""
        permission = DenyStaffListPermission()
        view = Mock(spec=['basename'])
        view.basename = 'customers'

        result = permission._get_permission_key(view, 'list')

        assert result == 'can_list_customers'

    def test_derives_list_key_from_model_name(self):
        """Should derive list permission key from queryset model name."""
        permission = DenyStaffListPermission()
        view = Mock(spec=['queryset'])
        view.queryset = Mock()
        view.queryset.model._meta.model_name = 'customer'

        result = permission._get_permission_key(view, 'list')

        assert result == 'can_list_customers'

    def test_returns_default_when_no_basename_or_queryset(self):
        """Should return default key when no basename or queryset."""
        permission = DenyStaffListPermission()
        view = Mock(spec=[])  # No basename or queryset

        result = permission._get_permission_key(view, 'list')

        assert result == 'can_list_resource'
