"""
Unit tests for the User model.

Tests cover:
1. Role-related methods (is_platform_user, is_tenant_user, can_invite_staff, etc.)
2. Permission checking methods (can_access_tickets, can_approve_plugins, etc.)
3. Property methods (full_name)
4. The save() method validation logic (tenant requirements for different roles)

Uses minimal User instances where possible to keep tests fast. Only uses @pytest.mark.django_db
for testing actual database constraints.
"""
import pytest
from unittest.mock import Mock, patch
from django.core.exceptions import ValidationError

from smoothschedule.identity.users.models import User


def create_user_instance(role, permissions=None, **kwargs):
    """
    Helper to create a minimal User instance for testing without hitting the DB.

    Args:
        role: User.Role value
        permissions: Dict of permissions (default: empty dict)
        **kwargs: Additional user attributes

    Returns:
        User instance (not saved to DB)
    """
    defaults = {
        'username': 'testuser',
        'email': 'test@example.com',
        'first_name': '',
        'last_name': '',
        'permissions': permissions or {},
    }
    defaults.update(kwargs)

    # Create instance without saving
    user = User(role=role, **defaults)
    # Set pk to simulate it exists (for methods that check)
    user.pk = 1
    return user


# =============================================================================
# Role-Related Method Tests
# =============================================================================

class TestRoleClassification:
    """Test is_platform_user() and is_tenant_user() methods."""

    def test_is_platform_user_returns_true_for_superuser(self):
        user = create_user_instance(User.Role.SUPERUSER)
        assert user.is_platform_user() is True

    def test_is_platform_user_returns_true_for_platform_manager(self):
        user = create_user_instance(User.Role.PLATFORM_MANAGER)
        assert user.is_platform_user() is True

    def test_is_platform_user_returns_true_for_platform_sales(self):
        user = create_user_instance(User.Role.PLATFORM_SALES)
        assert user.is_platform_user() is True

    def test_is_platform_user_returns_true_for_platform_support(self):
        user = create_user_instance(User.Role.PLATFORM_SUPPORT)
        assert user.is_platform_user() is True

    def test_is_platform_user_returns_false_for_tenant_owner(self):
        user = create_user_instance(User.Role.TENANT_OWNER)
        assert user.is_platform_user() is False

    def test_is_platform_user_returns_false_for_customer(self):
        user = create_user_instance(User.Role.CUSTOMER)
        assert user.is_platform_user() is False

    def test_is_tenant_user_returns_true_for_tenant_owner(self):
        user = create_user_instance(User.Role.TENANT_OWNER)
        assert user.is_tenant_user() is True

    def test_is_tenant_user_returns_true_for_tenant_manager(self):
        user = create_user_instance(User.Role.TENANT_MANAGER)
        assert user.is_tenant_user() is True

    def test_is_tenant_user_returns_true_for_tenant_staff(self):
        user = create_user_instance(User.Role.TENANT_STAFF)
        assert user.is_tenant_user() is True

    def test_is_tenant_user_returns_true_for_customer(self):
        user = create_user_instance(User.Role.CUSTOMER)
        assert user.is_tenant_user() is True

    def test_is_tenant_user_returns_false_for_superuser(self):
        user = create_user_instance(User.Role.SUPERUSER)
        assert user.is_tenant_user() is False


# =============================================================================
# User Management Permission Tests
# =============================================================================

class TestCanManageUsers:
    """Test can_manage_users() method."""

    def test_returns_true_for_superuser(self):
        user = create_user_instance(User.Role.SUPERUSER)
        assert user.can_manage_users() is True

    def test_returns_true_for_platform_manager(self):
        user = create_user_instance(User.Role.PLATFORM_MANAGER)
        assert user.can_manage_users() is True

    def test_returns_true_for_tenant_owner(self):
        user = create_user_instance(User.Role.TENANT_OWNER)
        assert user.can_manage_users() is True

    def test_returns_true_for_tenant_manager(self):
        user = create_user_instance(User.Role.TENANT_MANAGER)
        assert user.can_manage_users() is True

    def test_returns_false_for_tenant_staff(self):
        user = create_user_instance(User.Role.TENANT_STAFF)
        assert user.can_manage_users() is False

    def test_returns_false_for_customer(self):
        user = create_user_instance(User.Role.CUSTOMER)
        assert user.can_manage_users() is False


# =============================================================================
# Billing Access Tests
# =============================================================================

class TestCanAccessBilling:
    """Test can_access_billing() method."""

    def test_returns_true_for_superuser(self):
        user = create_user_instance(User.Role.SUPERUSER)
        assert user.can_access_billing() is True

    def test_returns_true_for_platform_manager(self):
        user = create_user_instance(User.Role.PLATFORM_MANAGER)
        assert user.can_access_billing() is True

    def test_returns_true_for_tenant_owner(self):
        user = create_user_instance(User.Role.TENANT_OWNER)
        assert user.can_access_billing() is True

    def test_returns_false_for_tenant_manager(self):
        user = create_user_instance(User.Role.TENANT_MANAGER)
        assert user.can_access_billing() is False

    def test_returns_false_for_tenant_staff(self):
        user = create_user_instance(User.Role.TENANT_STAFF)
        assert user.can_access_billing() is False


# =============================================================================
# Staff Invitation Permission Tests
# =============================================================================

class TestCanInviteStaff:
    """Test can_invite_staff() method."""

    def test_returns_true_for_tenant_owner(self):
        user = create_user_instance(User.Role.TENANT_OWNER)
        assert user.can_invite_staff() is True

    def test_returns_true_for_manager_with_permission(self):
        user = create_user_instance(User.Role.TENANT_MANAGER, permissions={'can_invite_staff': True})
        assert user.can_invite_staff() is True

    def test_returns_false_for_manager_without_permission(self):
        user = create_user_instance(User.Role.TENANT_MANAGER)
        assert user.can_invite_staff() is False

    def test_returns_false_for_manager_with_explicit_false_permission(self):
        user = create_user_instance(User.Role.TENANT_MANAGER, permissions={'can_invite_staff': False})
        assert user.can_invite_staff() is False

    def test_returns_false_for_tenant_staff(self):
        user = create_user_instance(User.Role.TENANT_STAFF)
        assert user.can_invite_staff() is False

    def test_returns_false_for_customer(self):
        user = create_user_instance(User.Role.CUSTOMER)
        assert user.can_invite_staff() is False


# =============================================================================
# Ticket Access Permission Tests
# =============================================================================

class TestCanAccessTickets:
    """Test can_access_tickets() method."""

    def test_returns_true_for_platform_user(self):
        user = create_user_instance(User.Role.PLATFORM_SUPPORT)
        assert user.can_access_tickets() is True

    def test_returns_true_for_tenant_owner(self):
        user = create_user_instance(User.Role.TENANT_OWNER)
        assert user.can_access_tickets() is True

    def test_returns_true_for_tenant_manager(self):
        user = create_user_instance(User.Role.TENANT_MANAGER)
        assert user.can_access_tickets() is True

    def test_returns_true_for_staff_with_permission(self):
        user = create_user_instance(User.Role.TENANT_STAFF, permissions={'can_access_tickets': True})
        assert user.can_access_tickets() is True

    def test_returns_false_for_staff_without_permission(self):
        user = create_user_instance(User.Role.TENANT_STAFF)
        assert user.can_access_tickets() is False

    def test_returns_true_for_customer(self):
        user = create_user_instance(User.Role.CUSTOMER)
        assert user.can_access_tickets() is True


# =============================================================================
# Plugin Approval Permission Tests
# =============================================================================

class TestCanApprovePlugins:
    """Test can_approve_plugins() method."""

    def test_returns_true_for_superuser(self):
        user = create_user_instance(User.Role.SUPERUSER)
        assert user.can_approve_plugins() is True

    def test_returns_true_for_platform_manager_with_permission(self):
        user = create_user_instance(User.Role.PLATFORM_MANAGER, permissions={'can_approve_plugins': True})
        assert user.can_approve_plugins() is True

    def test_returns_false_for_platform_manager_without_permission(self):
        user = create_user_instance(User.Role.PLATFORM_MANAGER)
        assert user.can_approve_plugins() is False

    def test_returns_true_for_platform_support_with_permission(self):
        user = create_user_instance(User.Role.PLATFORM_SUPPORT, permissions={'can_approve_plugins': True})
        assert user.can_approve_plugins() is True

    def test_returns_false_for_platform_support_without_permission(self):
        user = create_user_instance(User.Role.PLATFORM_SUPPORT)
        assert user.can_approve_plugins() is False

    def test_returns_false_for_tenant_owner(self):
        user = create_user_instance(User.Role.TENANT_OWNER)
        assert user.can_approve_plugins() is False


# =============================================================================
# URL Whitelist Permission Tests
# =============================================================================

class TestCanWhitelistUrls:
    """Test can_whitelist_urls() method."""

    def test_returns_true_for_superuser(self):
        user = create_user_instance(User.Role.SUPERUSER)
        assert user.can_whitelist_urls() is True

    def test_returns_true_for_platform_manager_with_permission(self):
        user = create_user_instance(User.Role.PLATFORM_MANAGER, permissions={'can_whitelist_urls': True})
        assert user.can_whitelist_urls() is True

    def test_returns_false_for_platform_manager_without_permission(self):
        user = create_user_instance(User.Role.PLATFORM_MANAGER)
        assert user.can_whitelist_urls() is False

    def test_returns_true_for_platform_support_with_permission(self):
        user = create_user_instance(User.Role.PLATFORM_SUPPORT, permissions={'can_whitelist_urls': True})
        assert user.can_whitelist_urls() is True

    def test_returns_false_for_tenant_owner(self):
        user = create_user_instance(User.Role.TENANT_OWNER)
        assert user.can_whitelist_urls() is False


# =============================================================================
# Time Off Self-Approval Tests
# =============================================================================

class TestCanSelfApproveTimeOff:
    """Test can_self_approve_time_off() method."""

    def test_returns_true_for_tenant_owner(self):
        user = create_user_instance(User.Role.TENANT_OWNER)
        assert user.can_self_approve_time_off() is True

    def test_returns_true_for_tenant_manager(self):
        user = create_user_instance(User.Role.TENANT_MANAGER)
        assert user.can_self_approve_time_off() is True

    def test_returns_true_for_staff_with_permission(self):
        user = create_user_instance(User.Role.TENANT_STAFF, permissions={'can_self_approve_time_off': True})
        assert user.can_self_approve_time_off() is True

    def test_returns_false_for_staff_without_permission(self):
        user = create_user_instance(User.Role.TENANT_STAFF)
        assert user.can_self_approve_time_off() is False

    def test_returns_false_for_customer(self):
        user = create_user_instance(User.Role.CUSTOMER)
        assert user.can_self_approve_time_off() is False


# =============================================================================
# Time Off Review Permission Tests
# =============================================================================

class TestCanReviewTimeOffRequests:
    """Test can_review_time_off_requests() method."""

    def test_returns_true_for_tenant_owner(self):
        user = create_user_instance(User.Role.TENANT_OWNER)
        assert user.can_review_time_off_requests() is True

    def test_returns_true_for_tenant_manager(self):
        user = create_user_instance(User.Role.TENANT_MANAGER)
        assert user.can_review_time_off_requests() is True

    def test_returns_false_for_tenant_staff(self):
        user = create_user_instance(User.Role.TENANT_STAFF)
        assert user.can_review_time_off_requests() is False

    def test_returns_false_for_customer(self):
        user = create_user_instance(User.Role.CUSTOMER)
        assert user.can_review_time_off_requests() is False


# =============================================================================
# Accessible Tenants Tests
# =============================================================================

class TestGetAccessibleTenants:
    """
    Test get_accessible_tenants() method.

    Note: These tests use database access because the method accesses
    ForeignKey relationships which trigger database queries even with mocking.
    """

    @pytest.mark.django_db
    def test_returns_all_tenants_for_platform_user(self):
        # Arrange
        from smoothschedule.identity.core.models import Tenant
        import uuid

        # Create a couple of tenants
        unique_id1 = str(uuid.uuid4())[:8]
        Tenant.objects.create(name=f"Tenant1 {unique_id1}", schema_name=f"tenant1{unique_id1}")

        unique_id2 = str(uuid.uuid4())[:8]
        Tenant.objects.create(name=f"Tenant2 {unique_id2}", schema_name=f"tenant2{unique_id2}")

        user = create_user_instance(User.Role.PLATFORM_MANAGER)

        # Act
        result = user.get_accessible_tenants()

        # Assert
        assert result.count() >= 2  # At least the two we created

    @pytest.mark.django_db
    def test_returns_single_tenant_for_tenant_user(self):
        # Arrange
        from smoothschedule.identity.core.models import Tenant
        import uuid

        unique_id = str(uuid.uuid4())[:8]
        tenant = Tenant.objects.create(name=f"My Business {unique_id}", schema_name=f"mybiz{unique_id}")

        user = User(
            username=f"owner{unique_id}",
            email=f"owner{unique_id}@test.com",
            role=User.Role.TENANT_OWNER,
            tenant=tenant
        )
        user.save()

        # Act
        result = user.get_accessible_tenants()

        # Assert
        assert result.count() == 1
        assert result.first() == tenant

    @pytest.mark.django_db
    def test_returns_empty_queryset_for_tenant_user_without_tenant(self):
        # Arrange
        user = create_user_instance(User.Role.TENANT_OWNER)
        # Force tenant to None (bypassing save validation)
        user.__dict__['tenant'] = None
        user.__dict__['tenant_id'] = None

        # Act
        result = user.get_accessible_tenants()

        # Assert
        assert result.count() == 0


# =============================================================================
# Full Name Property Tests
# =============================================================================

class TestFullNameProperty:
    """Test full_name property."""

    def test_returns_first_and_last_name(self):
        user = create_user_instance(User.Role.CUSTOMER, first_name="John", last_name="Doe", username="johndoe")
        assert user.full_name == "John Doe"

    def test_returns_only_first_name(self):
        user = create_user_instance(User.Role.CUSTOMER, first_name="John", last_name="", username="johndoe")
        assert user.full_name == "John"

    def test_returns_only_last_name(self):
        user = create_user_instance(User.Role.CUSTOMER, first_name="", last_name="Doe", username="johndoe")
        assert user.full_name == "Doe"

    def test_returns_username_when_no_names(self):
        user = create_user_instance(User.Role.CUSTOMER, first_name="", last_name="", username="johndoe")
        assert user.full_name == "johndoe"

    def test_returns_email_when_no_names_or_username(self):
        user = create_user_instance(User.Role.CUSTOMER, first_name="", last_name="", username="", email="john@example.com")
        assert user.full_name == "john@example.com"

    def test_strips_whitespace(self):
        user = create_user_instance(User.Role.CUSTOMER, first_name="  John  ", last_name="  Doe  ", username="johndoe")
        # Joined with space, then stripped
        assert user.full_name == "John     Doe"


# =============================================================================
# Save Method Validation Tests
# =============================================================================

class TestSaveMethodValidation:
    """Test the save() method's business logic validation."""

    @pytest.mark.django_db
    def test_sets_role_to_superuser_when_is_superuser_flag_set(self):
        # Arrange - Test Django's create_superuser compatibility
        user = User(
            username="admin",
            email="admin@example.com",
            is_superuser=True,
            role=User.Role.CUSTOMER  # Wrong role, should be corrected
        )

        # Act
        user.save()

        # Assert
        assert user.role == User.Role.SUPERUSER
        assert user.is_staff is True
        assert user.is_superuser is True

    @pytest.mark.django_db
    def test_sets_is_staff_and_is_superuser_for_superuser_role(self):
        # Arrange
        user = User(
            username="admin2",
            email="admin2@example.com",
            role=User.Role.SUPERUSER,
            is_staff=False,
            is_superuser=False
        )

        # Act
        user.save()

        # Assert
        assert user.is_staff is True
        assert user.is_superuser is True

    @pytest.mark.django_db
    def test_clears_tenant_for_platform_users(self):
        # Arrange
        from smoothschedule.identity.core.models import Tenant
        import uuid

        # Create a tenant first with unique schema_name
        unique_id = str(uuid.uuid4())[:8]
        tenant = Tenant.objects.create(
            name=f"Test Business {unique_id}",
            schema_name=f"testbiz{unique_id}"
        )

        user = User(
            username=f"platformuser{unique_id}",
            email=f"platform{unique_id}@example.com",
            role=User.Role.PLATFORM_MANAGER,
            tenant=tenant  # Should be cleared
        )

        # Act
        user.save()

        # Assert
        assert user.tenant is None

    @pytest.mark.django_db
    def test_raises_error_for_tenant_user_without_tenant(self):
        # Arrange
        import uuid
        unique_id = str(uuid.uuid4())[:8]

        user = User(
            username=f"tenantuser{unique_id}",
            email=f"tenant{unique_id}@example.com",
            role=User.Role.TENANT_OWNER,
            tenant=None  # Missing required tenant
        )

        # Act & Assert
        with pytest.raises(ValueError) as exc_info:
            user.save()

        assert "must be assigned to a tenant" in str(exc_info.value)

    @pytest.mark.django_db
    def test_allows_tenant_user_with_tenant(self):
        # Arrange
        from smoothschedule.identity.core.models import Tenant
        import uuid

        unique_id = str(uuid.uuid4())[:8]
        tenant = Tenant.objects.create(
            name=f"Test Business {unique_id}",
            schema_name=f"testbiz{unique_id}"
        )

        user = User(
            username=f"owner{unique_id}",
            email=f"owner{unique_id}@testbiz.com",
            role=User.Role.TENANT_OWNER,
            tenant=tenant
        )

        # Act
        user.save()

        # Assert
        assert user.tenant == tenant
        assert user.id is not None

    @pytest.mark.django_db
    def test_allows_customer_with_tenant(self):
        # Arrange
        from smoothschedule.identity.core.models import Tenant
        import uuid

        unique_id = str(uuid.uuid4())[:8]
        tenant = Tenant.objects.create(
            name=f"Test Business {unique_id}",
            schema_name=f"testbiz{unique_id}"
        )

        user = User(
            username=f"customer{unique_id}",
            email=f"customer{unique_id}@example.com",
            role=User.Role.CUSTOMER,
            tenant=tenant
        )

        # Act
        user.save()

        # Assert
        assert user.tenant == tenant


# =============================================================================
# String Representation Tests
# =============================================================================

class TestUserStringRepresentation:
    """Test the __str__() method."""

    def test_str_returns_email_and_role_display(self):
        # Arrange
        user = create_user_instance(User.Role.PLATFORM_MANAGER, email="john@example.com")

        # Act
        result = str(user)

        # Assert
        assert result == "john@example.com (Platform Manager)"
