"""Unit tests for Pydantic models."""

from datetime import datetime, timedelta, timezone
from uuid import UUID, uuid4

import pytest
from pydantic import ValidationError

from teams_phone.models import (
    AccountType,
    ActivationState,
    AssignmentCategory,
    AssignmentStatus,
    AsyncOperation,
    AuthMethod,
    AuthStatus,
    AuthStatusType,
    CachedToken,
    CacheSettings,
    Config,
    EffectivePolicyAssignment,
    EmergencyLocation,
    GraphErrorDetail,
    GraphErrorResponse,
    GraphListResponse,
    NetworkSettings,
    NumberAssignment,
    NumberType,
    OperationNumber,
    OperationStatus,
    Policy,
    PolicyAssignmentDetail,
    TelephoneNumber,
    TenantProfile,
    UserConfiguration,
)


class TestAuthMethodEnum:
    """Tests for AuthMethod enum."""

    @pytest.mark.unit
    def test_certificate_value(self) -> None:
        """Certificate auth method has correct value."""
        assert AuthMethod.CERTIFICATE.value == "certificate"

    @pytest.mark.unit
    def test_client_secret_value(self) -> None:
        """Client secret auth method has correct value."""
        assert AuthMethod.CLIENT_SECRET.value == "client-secret"

    @pytest.mark.unit
    def test_is_string_enum(self) -> None:
        """AuthMethod is a string enum."""
        assert isinstance(AuthMethod.CERTIFICATE, str)
        assert isinstance(AuthMethod.CLIENT_SECRET, str)


class TestTenantProfile:
    """Tests for TenantProfile model."""

    @pytest.mark.unit
    def test_create_minimal_profile(self) -> None:
        """TenantProfile can be created with required fields only."""
        tenant_id = uuid4()
        client_id = uuid4()
        profile = TenantProfile(
            name="contoso",
            tenant_id=tenant_id,
            client_id=client_id,
            display_name="Contoso Corp",
            auth_method=AuthMethod.CERTIFICATE,
        )
        assert profile.name == "contoso"
        assert profile.tenant_id == tenant_id
        assert profile.client_id == client_id
        assert profile.display_name == "Contoso Corp"
        assert profile.auth_method == AuthMethod.CERTIFICATE
        assert profile.cert_path is None
        assert profile.default_location is None

    @pytest.mark.unit
    def test_create_full_profile(self) -> None:
        """TenantProfile can be created with all fields."""
        profile = TenantProfile(
            name="contoso",
            tenant_id=uuid4(),
            client_id=uuid4(),
            display_name="Contoso Corp",
            auth_method=AuthMethod.CERTIFICATE,
            cert_path="/path/to/cert.pem",
            default_location="Main Office",
        )
        assert profile.cert_path == "/path/to/cert.pem"
        assert profile.default_location == "Main Office"

    @pytest.mark.unit
    def test_uuid_from_string(self) -> None:
        """TenantProfile accepts UUID strings for tenant_id and client_id."""
        tenant_uuid = "12345678-1234-5678-1234-567812345678"
        client_uuid = "87654321-4321-8765-4321-876543218765"
        profile = TenantProfile(
            name="test",
            tenant_id=tenant_uuid,  # type: ignore[arg-type]
            client_id=client_uuid,  # type: ignore[arg-type]
            display_name="Test",
            auth_method=AuthMethod.CERTIFICATE,
        )
        assert profile.tenant_id == UUID(tenant_uuid)
        assert profile.client_id == UUID(client_uuid)

    @pytest.mark.unit
    def test_auth_method_from_string(self) -> None:
        """TenantProfile accepts auth_method as string."""
        profile = TenantProfile(
            name="test",
            tenant_id=uuid4(),
            client_id=uuid4(),
            display_name="Test",
            auth_method="certificate",  # type: ignore[arg-type]
        )
        assert profile.auth_method == AuthMethod.CERTIFICATE

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """TenantProfile rejects unknown fields (extra='forbid')."""
        with pytest.raises(ValidationError) as exc_info:
            TenantProfile(
                name="test",
                tenant_id=uuid4(),
                client_id=uuid4(),
                display_name="Test",
                auth_method=AuthMethod.CERTIFICATE,
                unknown_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_invalid_uuid_rejected(self) -> None:
        """TenantProfile rejects invalid UUID strings."""
        with pytest.raises(ValidationError) as exc_info:
            TenantProfile(
                name="test",
                tenant_id="not-a-uuid",  # type: ignore[arg-type]
                client_id=uuid4(),
                display_name="Test",
                auth_method=AuthMethod.CERTIFICATE,
            )
        assert "uuid" in str(exc_info.value).lower()

    @pytest.mark.unit
    def test_invalid_auth_method_rejected(self) -> None:
        """TenantProfile rejects invalid auth method."""
        with pytest.raises(ValidationError) as exc_info:
            TenantProfile(
                name="test",
                tenant_id=uuid4(),
                client_id=uuid4(),
                display_name="Test",
                auth_method="invalid-method",  # type: ignore[arg-type]
            )
        assert "auth_method" in str(exc_info.value).lower()

    @pytest.mark.unit
    def test_serialization(self) -> None:
        """TenantProfile serializes to dict correctly."""
        tenant_id = uuid4()
        client_id = uuid4()
        profile = TenantProfile(
            name="contoso",
            tenant_id=tenant_id,
            client_id=client_id,
            display_name="Contoso Corp",
            auth_method=AuthMethod.CERTIFICATE,
        )
        data = profile.model_dump()
        assert data["name"] == "contoso"
        assert data["tenant_id"] == tenant_id
        assert data["client_id"] == client_id
        assert data["auth_method"] == AuthMethod.CERTIFICATE
        assert data["cert_path"] is None

    @pytest.mark.unit
    def test_json_serialization(self) -> None:
        """TenantProfile serializes to JSON correctly."""
        tenant_id = uuid4()
        profile = TenantProfile(
            name="test",
            tenant_id=tenant_id,
            client_id=uuid4(),
            display_name="Test",
            auth_method=AuthMethod.CLIENT_SECRET,
        )
        json_str = profile.model_dump_json()
        assert str(tenant_id) in json_str
        assert "client-secret" in json_str


class TestCachedToken:
    """Tests for CachedToken model."""

    @pytest.mark.unit
    def test_create_token(self) -> None:
        """CachedToken can be created with all fields."""
        expires_at = datetime.now(timezone.utc) + timedelta(hours=1)
        token = CachedToken(
            access_token="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiI...",
            expires_at=expires_at,
            tenant_id="12345678-1234-5678-1234-567812345678",
            scopes=["https://graph.microsoft.com/.default"],
        )
        assert token.access_token.startswith("eyJ")
        assert token.expires_at == expires_at
        assert token.tenant_id == "12345678-1234-5678-1234-567812345678"
        assert len(token.scopes) == 1

    @pytest.mark.unit
    def test_is_expired_false(self) -> None:
        """is_expired returns False for future expiration."""
        token = CachedToken(
            access_token="token",
            expires_at=datetime.now(timezone.utc) + timedelta(hours=1),
            tenant_id="test",
            scopes=[],
        )
        assert token.is_expired() is False

    @pytest.mark.unit
    def test_is_expired_true(self) -> None:
        """is_expired returns True for past expiration."""
        token = CachedToken(
            access_token="token",
            expires_at=datetime.now(timezone.utc) - timedelta(hours=1),
            tenant_id="test",
            scopes=[],
        )
        assert token.is_expired() is True

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """CachedToken rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            CachedToken(
                access_token="token",
                expires_at=datetime.now(timezone.utc),
                tenant_id="test",
                scopes=[],
                refresh_token="should-fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)


class TestAuthStatusType:
    """Tests for AuthStatusType enum."""

    @pytest.mark.unit
    def test_authenticated_value(self) -> None:
        """Authenticated status has correct value."""
        assert AuthStatusType.AUTHENTICATED.value == "authenticated"

    @pytest.mark.unit
    def test_unauthenticated_value(self) -> None:
        """Unauthenticated status has correct value."""
        assert AuthStatusType.UNAUTHENTICATED.value == "unauthenticated"

    @pytest.mark.unit
    def test_expired_value(self) -> None:
        """Expired status has correct value."""
        assert AuthStatusType.EXPIRED.value == "expired"


class TestAuthStatus:
    """Tests for AuthStatus model."""

    @pytest.mark.unit
    def test_create_authenticated_status(self) -> None:
        """AuthStatus can represent authenticated state."""
        expires_at = datetime.now(timezone.utc) + timedelta(hours=1)
        status = AuthStatus(
            status=AuthStatusType.AUTHENTICATED,
            tenant_name="contoso",
            tenant_id="12345678-1234-5678-1234-567812345678",
            expires_at=expires_at,
        )
        assert status.status == AuthStatusType.AUTHENTICATED
        assert status.tenant_name == "contoso"
        assert status.expires_at == expires_at
        assert status.error_message is None

    @pytest.mark.unit
    def test_create_unauthenticated_status(self) -> None:
        """AuthStatus can represent unauthenticated state."""
        status = AuthStatus(status=AuthStatusType.UNAUTHENTICATED)
        assert status.status == AuthStatusType.UNAUTHENTICATED
        assert status.tenant_name is None
        assert status.tenant_id is None

    @pytest.mark.unit
    def test_create_expired_status_with_error(self) -> None:
        """AuthStatus can include error message."""
        status = AuthStatus(
            status=AuthStatusType.EXPIRED,
            tenant_name="contoso",
            error_message="Token expired at 2024-01-15 10:00:00",
        )
        assert status.status == AuthStatusType.EXPIRED
        assert status.error_message is not None

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """AuthStatus rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            AuthStatus(
                status=AuthStatusType.AUTHENTICATED,
                unknown="field",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)


class TestCacheSettings:
    """Tests for CacheSettings model."""

    @pytest.mark.unit
    def test_default_values(self) -> None:
        """CacheSettings uses default values when not provided."""
        settings = CacheSettings()
        assert settings.cache_path == "~/.teams-phone/cache"
        assert settings.stale_days == 30

    @pytest.mark.unit
    def test_custom_values(self) -> None:
        """CacheSettings accepts custom values."""
        settings = CacheSettings(
            cache_path="/custom/cache",
            stale_days=7,
        )
        assert settings.cache_path == "/custom/cache"
        assert settings.stale_days == 7

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """CacheSettings rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            CacheSettings(extra="field")  # type: ignore[call-arg]
        assert "extra_forbidden" in str(exc_info.value)


class TestNetworkSettings:
    """Tests for NetworkSettings model."""

    @pytest.mark.unit
    def test_default_values(self) -> None:
        """NetworkSettings uses default values when not provided."""
        settings = NetworkSettings()
        assert settings.timeout == 30
        assert settings.max_retries == 3

    @pytest.mark.unit
    def test_custom_values(self) -> None:
        """NetworkSettings accepts custom values."""
        settings = NetworkSettings(
            timeout=60,
            max_retries=5,
        )
        assert settings.timeout == 60
        assert settings.max_retries == 5


class TestConfig:
    """Tests for Config model."""

    @pytest.mark.unit
    def test_default_values(self) -> None:
        """Config uses default values when not provided."""
        config = Config()
        assert config.config_path == "~/.teams-phone/config.toml"
        assert config.token_path == "~/.teams-phone/tokens"
        assert config.default_tenant is None
        assert config.cache.cache_path == "~/.teams-phone/cache"
        assert config.network.timeout == 30
        assert config.tenants == {}

    @pytest.mark.unit
    def test_with_tenant(self) -> None:
        """Config can contain tenant profiles."""
        profile = TenantProfile(
            name="contoso",
            tenant_id=uuid4(),
            client_id=uuid4(),
            display_name="Contoso Corp",
            auth_method=AuthMethod.CERTIFICATE,
        )
        config = Config(
            default_tenant="contoso",
            tenants={"contoso": profile},
        )
        assert config.default_tenant == "contoso"
        assert "contoso" in config.tenants
        assert config.tenants["contoso"].display_name == "Contoso Corp"

    @pytest.mark.unit
    def test_with_custom_settings(self) -> None:
        """Config can have custom cache and network settings."""
        config = Config(
            cache=CacheSettings(stale_days=7),
            network=NetworkSettings(timeout=60),
        )
        assert config.cache.stale_days == 7
        assert config.network.timeout == 60

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """Config rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            Config(unknown="field")  # type: ignore[call-arg]
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_serialization(self) -> None:
        """Config serializes to dict correctly."""
        config = Config(default_tenant="test")
        data = config.model_dump()
        assert data["default_tenant"] == "test"
        assert "cache" in data
        assert "network" in data
        assert "tenants" in data

    @pytest.mark.unit
    def test_nested_tenant_serialization(self) -> None:
        """Config with tenants serializes correctly."""
        tenant_id = uuid4()
        profile = TenantProfile(
            name="test",
            tenant_id=tenant_id,
            client_id=uuid4(),
            display_name="Test",
            auth_method=AuthMethod.CERTIFICATE,
        )
        config = Config(tenants={"test": profile})
        data = config.model_dump()
        assert data["tenants"]["test"]["tenant_id"] == tenant_id


class TestAccountTypeEnum:
    """Tests for AccountType enum."""

    @pytest.mark.unit
    def test_user_value(self) -> None:
        """User account type has correct value."""
        assert AccountType.USER.value == "user"

    @pytest.mark.unit
    def test_resource_account_value(self) -> None:
        """Resource account type has correct value."""
        assert AccountType.RESOURCE_ACCOUNT.value == "resourceAccount"

    @pytest.mark.unit
    def test_guest_value(self) -> None:
        """Guest account type has correct value."""
        assert AccountType.GUEST.value == "guest"

    @pytest.mark.unit
    def test_sfb_on_prem_user_value(self) -> None:
        """SfB on-prem user account type has correct value."""
        assert AccountType.SFB_ON_PREM_USER.value == "sfbOnPremUser"

    @pytest.mark.unit
    def test_unknown_value(self) -> None:
        """Unknown account type has correct value."""
        assert AccountType.UNKNOWN.value == "unknown"

    @pytest.mark.unit
    def test_is_string_enum(self) -> None:
        """AccountType is a string enum."""
        assert isinstance(AccountType.USER, str)
        assert isinstance(AccountType.RESOURCE_ACCOUNT, str)


class TestTelephoneNumber:
    """Tests for TelephoneNumber model."""

    @pytest.mark.unit
    def test_create_from_snake_case(self) -> None:
        """TelephoneNumber can be created with snake_case field names (populate_by_name)."""
        # Using model_validate to test snake_case (mypy only knows alias)
        phone = TelephoneNumber.model_validate(
            {"telephone_number": "+13235533696", "assignment_category": "primary"}
        )
        assert phone.telephone_number == "+13235533696"
        assert phone.assignment_category == "primary"

    @pytest.mark.unit
    def test_create_from_camel_case(self) -> None:
        """TelephoneNumber can be created with camelCase aliases."""
        phone = TelephoneNumber(
            telephoneNumber="+13235533696",
            assignmentCategory="primary",
        )
        assert phone.telephone_number == "+13235533696"
        assert phone.assignment_category == "primary"

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """TelephoneNumber rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            TelephoneNumber(
                telephoneNumber="+13235533696",
                assignmentCategory="primary",
                extra_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_missing_required_field_rejected(self) -> None:
        """TelephoneNumber requires all fields."""
        with pytest.raises(ValidationError):
            TelephoneNumber(
                telephoneNumber="+13235533696",
                # Missing assignmentCategory
            )  # type: ignore[call-arg]


class TestPolicyAssignmentDetail:
    """Tests for PolicyAssignmentDetail model."""

    @pytest.mark.unit
    def test_create_minimal(self) -> None:
        """PolicyAssignmentDetail can be created without group_id."""
        detail = PolicyAssignmentDetail(
            displayName="Global",
            assignmentType="direct",
            policyId="tag:global",
        )
        assert detail.display_name == "Global"
        assert detail.assignment_type == "direct"
        assert detail.policy_id == "tag:global"
        assert detail.group_id is None

    @pytest.mark.unit
    def test_create_with_group_id(self) -> None:
        """PolicyAssignmentDetail can include group_id."""
        group_id = "12345678-1234-5678-1234-567812345678"
        detail = PolicyAssignmentDetail(
            displayName="Sales Policy",
            assignmentType="group",
            policyId="tag:sales-policy",
            groupId=group_id,
        )
        assert detail.group_id == group_id

    @pytest.mark.unit
    def test_create_from_snake_case(self) -> None:
        """PolicyAssignmentDetail can be created with snake_case field names (populate_by_name)."""
        # Using model_validate to test snake_case (mypy only knows alias)
        detail = PolicyAssignmentDetail.model_validate(
            {
                "display_name": "Global",
                "assignment_type": "direct",
                "policy_id": "tag:global",
                "group_id": None,
            }
        )
        assert detail.display_name == "Global"
        assert detail.assignment_type == "direct"

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """PolicyAssignmentDetail rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            PolicyAssignmentDetail(
                displayName="Global",
                assignmentType="direct",
                policyId="tag:global",
                unknown_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)


class TestEffectivePolicyAssignment:
    """Tests for EffectivePolicyAssignment model."""

    @pytest.mark.unit
    def test_create_with_nested_detail(self) -> None:
        """EffectivePolicyAssignment contains nested PolicyAssignmentDetail."""
        detail = PolicyAssignmentDetail(
            displayName="Global",
            assignmentType="direct",
            policyId="tag:global",
        )
        assignment = EffectivePolicyAssignment(
            policyType="TeamsCallingPolicy",
            policyAssignment=detail,
        )
        assert assignment.policy_type == "TeamsCallingPolicy"
        assert assignment.policy_assignment.display_name == "Global"

    @pytest.mark.unit
    def test_create_from_dict(self) -> None:
        """EffectivePolicyAssignment can be created from nested dict."""
        assignment = EffectivePolicyAssignment.model_validate(
            {
                "policyType": "TeamsCallingPolicy",
                "policyAssignment": {
                    "displayName": "Global",
                    "assignmentType": "direct",
                    "policyId": "tag:global",
                },
            }
        )
        assert assignment.policy_type == "TeamsCallingPolicy"
        assert assignment.policy_assignment.display_name == "Global"

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """EffectivePolicyAssignment rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            EffectivePolicyAssignment(
                policyType="TeamsCallingPolicy",
                policyAssignment=PolicyAssignmentDetail(
                    displayName="Global",
                    assignmentType="direct",
                    policyId="tag:global",
                ),
                extra_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_nested_extra_field_rejected(self) -> None:
        """EffectivePolicyAssignment rejects extra fields in nested model."""
        with pytest.raises(ValidationError) as exc_info:
            EffectivePolicyAssignment.model_validate(
                {
                    "policyType": "TeamsCallingPolicy",
                    "policyAssignment": {
                        "displayName": "Global",
                        "assignmentType": "direct",
                        "policyId": "tag:global",
                        "extraField": "should fail",
                    },
                }
            )
        assert "extra_forbidden" in str(exc_info.value)


class TestUserConfiguration:
    """Tests for UserConfiguration model."""

    @pytest.mark.unit
    def test_create_minimal(self) -> None:
        """UserConfiguration can be created with required fields only."""
        user = UserConfiguration(
            id="12345678-1234-5678-1234-567812345678",
            userPrincipalName="user@contoso.com",
            tenantId="87654321-4321-8765-4321-876543218765",
            accountType=AccountType.USER,
            isEnterpriseVoiceEnabled=True,
        )
        assert user.id == "12345678-1234-5678-1234-567812345678"
        assert user.user_principal_name == "user@contoso.com"
        assert user.tenant_id == "87654321-4321-8765-4321-876543218765"
        assert user.account_type == AccountType.USER
        assert user.is_enterprise_voice_enabled is True
        assert user.display_name is None
        assert user.feature_types == []
        assert user.telephone_numbers == []
        assert user.effective_policy_assignments == []
        assert user.created_date_time is None
        assert user.modified_date_time is None

    @pytest.mark.unit
    def test_create_full(self) -> None:
        """UserConfiguration can be created with all fields."""
        created = datetime(2024, 1, 15, 10, 0, 0)
        modified = datetime(2024, 1, 20, 14, 30, 0)

        user = UserConfiguration(
            id="12345678-1234-5678-1234-567812345678",
            userPrincipalName="user@contoso.com",
            tenantId="87654321-4321-8765-4321-876543218765",
            displayName="John Doe",
            accountType=AccountType.USER,
            isEnterpriseVoiceEnabled=True,
            featureTypes=["Teams", "CallingPlan"],
            telephoneNumbers=[
                TelephoneNumber(
                    telephoneNumber="+13235533696",
                    assignmentCategory="primary",
                )
            ],
            effectivePolicyAssignments=[
                EffectivePolicyAssignment(
                    policyType="TeamsCallingPolicy",
                    policyAssignment=PolicyAssignmentDetail(
                        displayName="Global",
                        assignmentType="direct",
                        policyId="tag:global",
                    ),
                )
            ],
            createdDateTime=created,
            modifiedDateTime=modified,
        )
        assert user.display_name == "John Doe"
        assert user.feature_types == ["Teams", "CallingPlan"]
        assert len(user.telephone_numbers) == 1
        assert user.telephone_numbers[0].telephone_number == "+13235533696"
        assert len(user.effective_policy_assignments) == 1
        assert user.effective_policy_assignments[0].policy_type == "TeamsCallingPolicy"
        assert user.created_date_time == created
        assert user.modified_date_time == modified

    @pytest.mark.unit
    def test_create_from_graph_api_response(self) -> None:
        """UserConfiguration can be created from Graph API JSON response."""
        api_response = {
            "id": "12345678-1234-5678-1234-567812345678",
            "userPrincipalName": "user@contoso.com",
            "tenantId": "87654321-4321-8765-4321-876543218765",
            "displayName": "John Doe",
            "accountType": "user",
            "isEnterpriseVoiceEnabled": True,
            "featureTypes": ["Teams", "CallingPlan"],
            "telephoneNumbers": [
                {
                    "telephoneNumber": "+13235533696",
                    "assignmentCategory": "primary",
                }
            ],
            "effectivePolicyAssignments": [
                {
                    "policyType": "TeamsCallingPolicy",
                    "policyAssignment": {
                        "displayName": "Global",
                        "assignmentType": "direct",
                        "policyId": "tag:global",
                    },
                }
            ],
            "createdDateTime": "2024-01-15T10:00:00Z",
            "modifiedDateTime": "2024-01-20T14:30:00Z",
        }
        user = UserConfiguration.model_validate(api_response)
        assert user.id == "12345678-1234-5678-1234-567812345678"
        assert user.user_principal_name == "user@contoso.com"
        assert user.display_name == "John Doe"
        assert user.account_type == AccountType.USER
        assert len(user.telephone_numbers) == 1
        assert user.telephone_numbers[0].assignment_category == "primary"

    @pytest.mark.unit
    def test_account_type_from_string(self) -> None:
        """UserConfiguration parses account_type from string."""
        user = UserConfiguration(
            id="12345678-1234-5678-1234-567812345678",
            userPrincipalName="resource@contoso.com",
            tenantId="87654321-4321-8765-4321-876543218765",
            accountType="resourceAccount",  # type: ignore[arg-type]
            isEnterpriseVoiceEnabled=False,
        )
        assert user.account_type == AccountType.RESOURCE_ACCOUNT

    @pytest.mark.unit
    def test_invalid_account_type_rejected(self) -> None:
        """UserConfiguration rejects invalid account type."""
        with pytest.raises(ValidationError) as exc_info:
            UserConfiguration(
                id="12345678-1234-5678-1234-567812345678",
                userPrincipalName="user@contoso.com",
                tenantId="87654321-4321-8765-4321-876543218765",
                accountType="invalid-type",  # type: ignore[arg-type]
                isEnterpriseVoiceEnabled=True,
            )
        assert "accounttype" in str(exc_info.value).lower()

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """UserConfiguration rejects unknown fields (extra='forbid')."""
        with pytest.raises(ValidationError) as exc_info:
            UserConfiguration(
                id="12345678-1234-5678-1234-567812345678",
                userPrincipalName="user@contoso.com",
                tenantId="87654321-4321-8765-4321-876543218765",
                accountType=AccountType.USER,
                isEnterpriseVoiceEnabled=True,
                unknown_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_missing_required_field_rejected(self) -> None:
        """UserConfiguration requires essential fields."""
        with pytest.raises(ValidationError):
            UserConfiguration(
                id="12345678-1234-5678-1234-567812345678",
                # Missing userPrincipalName, tenantId, accountType, isEnterpriseVoiceEnabled
            )  # type: ignore[call-arg]

    @pytest.mark.unit
    def test_serialization(self) -> None:
        """UserConfiguration serializes to dict correctly."""
        user = UserConfiguration(
            id="12345678-1234-5678-1234-567812345678",
            userPrincipalName="user@contoso.com",
            tenantId="87654321-4321-8765-4321-876543218765",
            displayName="John Doe",
            accountType=AccountType.USER,
            isEnterpriseVoiceEnabled=True,
        )
        data = user.model_dump()
        assert data["id"] == "12345678-1234-5678-1234-567812345678"
        assert data["user_principal_name"] == "user@contoso.com"
        assert data["account_type"] == AccountType.USER
        assert data["feature_types"] == []

    @pytest.mark.unit
    def test_serialization_by_alias(self) -> None:
        """UserConfiguration serializes with camelCase aliases."""
        user = UserConfiguration(
            id="12345678-1234-5678-1234-567812345678",
            userPrincipalName="user@contoso.com",
            tenantId="87654321-4321-8765-4321-876543218765",
            accountType=AccountType.USER,
            isEnterpriseVoiceEnabled=True,
        )
        data = user.model_dump(by_alias=True)
        assert "userPrincipalName" in data
        assert "isEnterpriseVoiceEnabled" in data
        assert data["userPrincipalName"] == "user@contoso.com"

    @pytest.mark.unit
    def test_nested_extra_field_rejected_in_telephone(self) -> None:
        """UserConfiguration rejects extra fields in nested TelephoneNumber."""
        with pytest.raises(ValidationError) as exc_info:
            UserConfiguration.model_validate(
                {
                    "id": "12345678-1234-5678-1234-567812345678",
                    "userPrincipalName": "user@contoso.com",
                    "tenantId": "87654321-4321-8765-4321-876543218765",
                    "accountType": "user",
                    "isEnterpriseVoiceEnabled": True,
                    "telephoneNumbers": [
                        {
                            "telephoneNumber": "+13235533696",
                            "assignmentCategory": "primary",
                            "extraField": "should fail",
                        }
                    ],
                }
            )
        assert "extra_forbidden" in str(exc_info.value)


class TestNumberTypeEnum:
    """Tests for NumberType enum."""

    @pytest.mark.unit
    def test_direct_routing_value(self) -> None:
        """Direct routing number type has correct value."""
        assert NumberType.DIRECT_ROUTING.value == "directRouting"

    @pytest.mark.unit
    def test_calling_plan_value(self) -> None:
        """Calling plan number type has correct value."""
        assert NumberType.CALLING_PLAN.value == "callingPlan"

    @pytest.mark.unit
    def test_operator_connect_value(self) -> None:
        """Operator connect number type has correct value."""
        assert NumberType.OPERATOR_CONNECT.value == "operatorConnect"

    @pytest.mark.unit
    def test_is_string_enum(self) -> None:
        """NumberType is a string enum."""
        assert isinstance(NumberType.DIRECT_ROUTING, str)
        assert isinstance(NumberType.CALLING_PLAN, str)
        assert isinstance(NumberType.OPERATOR_CONNECT, str)


class TestActivationStateEnum:
    """Tests for ActivationState enum."""

    @pytest.mark.unit
    def test_activated_value(self) -> None:
        """Activated state has correct value."""
        assert ActivationState.ACTIVATED.value == "activated"

    @pytest.mark.unit
    def test_assignment_pending_value(self) -> None:
        """Assignment pending state has correct value."""
        assert ActivationState.ASSIGNMENT_PENDING.value == "assignmentPending"

    @pytest.mark.unit
    def test_assignment_failed_value(self) -> None:
        """Assignment failed state has correct value."""
        assert ActivationState.ASSIGNMENT_FAILED.value == "assignmentFailed"

    @pytest.mark.unit
    def test_update_pending_value(self) -> None:
        """Update pending state has correct value."""
        assert ActivationState.UPDATE_PENDING.value == "updatePending"

    @pytest.mark.unit
    def test_update_failed_value(self) -> None:
        """Update failed state has correct value."""
        assert ActivationState.UPDATE_FAILED.value == "updateFailed"

    @pytest.mark.unit
    def test_is_string_enum(self) -> None:
        """ActivationState is a string enum."""
        assert isinstance(ActivationState.ACTIVATED, str)
        assert isinstance(ActivationState.ASSIGNMENT_PENDING, str)


class TestAssignmentStatusEnum:
    """Tests for AssignmentStatus enum."""

    @pytest.mark.unit
    def test_unassigned_value(self) -> None:
        """Unassigned status has correct value."""
        assert AssignmentStatus.UNASSIGNED.value == "unassigned"

    @pytest.mark.unit
    def test_user_assigned_value(self) -> None:
        """User assigned status has correct value."""
        assert AssignmentStatus.USER_ASSIGNED.value == "userAssigned"

    @pytest.mark.unit
    def test_conference_assigned_value(self) -> None:
        """Conference assigned status has correct value."""
        assert AssignmentStatus.CONFERENCE_ASSIGNED.value == "conferenceAssigned"

    @pytest.mark.unit
    def test_voice_application_assigned_value(self) -> None:
        """Voice application assigned status has correct value."""
        assert (
            AssignmentStatus.VOICE_APPLICATION_ASSIGNED.value
            == "voiceApplicationAssigned"
        )

    @pytest.mark.unit
    def test_is_string_enum(self) -> None:
        """AssignmentStatus is a string enum."""
        assert isinstance(AssignmentStatus.UNASSIGNED, str)
        assert isinstance(AssignmentStatus.USER_ASSIGNED, str)


class TestAssignmentCategoryEnum:
    """Tests for AssignmentCategory enum."""

    @pytest.mark.unit
    def test_primary_value(self) -> None:
        """Primary category has correct value."""
        assert AssignmentCategory.PRIMARY.value == "primary"

    @pytest.mark.unit
    def test_private_value(self) -> None:
        """Private category has correct value."""
        assert AssignmentCategory.PRIVATE.value == "private"

    @pytest.mark.unit
    def test_alternate_value(self) -> None:
        """Alternate category has correct value."""
        assert AssignmentCategory.ALTERNATE.value == "alternate"

    @pytest.mark.unit
    def test_is_string_enum(self) -> None:
        """AssignmentCategory is a string enum."""
        assert isinstance(AssignmentCategory.PRIMARY, str)
        assert isinstance(AssignmentCategory.PRIVATE, str)
        assert isinstance(AssignmentCategory.ALTERNATE, str)


class TestOperationStatusEnum:
    """Tests for OperationStatus enum."""

    @pytest.mark.unit
    def test_not_started_value(self) -> None:
        """Not started status has correct value."""
        assert OperationStatus.NOT_STARTED.value == "notStarted"

    @pytest.mark.unit
    def test_running_value(self) -> None:
        """Running status has correct value."""
        assert OperationStatus.RUNNING.value == "running"

    @pytest.mark.unit
    def test_succeeded_value(self) -> None:
        """Succeeded status has correct value."""
        assert OperationStatus.SUCCEEDED.value == "succeeded"

    @pytest.mark.unit
    def test_failed_value(self) -> None:
        """Failed status has correct value."""
        assert OperationStatus.FAILED.value == "failed"

    @pytest.mark.unit
    def test_skipped_value(self) -> None:
        """Skipped status has correct value."""
        assert OperationStatus.SKIPPED.value == "skipped"

    @pytest.mark.unit
    def test_unknown_future_value(self) -> None:
        """Unknown future value status has correct value."""
        assert OperationStatus.UNKNOWN_FUTURE_VALUE.value == "unknownFutureValue"

    @pytest.mark.unit
    def test_is_string_enum(self) -> None:
        """OperationStatus is a string enum."""
        assert isinstance(OperationStatus.NOT_STARTED, str)
        assert isinstance(OperationStatus.SUCCEEDED, str)
        assert isinstance(OperationStatus.FAILED, str)


class TestOperationNumber:
    """Tests for OperationNumber model."""

    @pytest.mark.unit
    def test_create_from_camel_case(self) -> None:
        """OperationNumber can be created with camelCase aliases."""
        op_num = OperationNumber(
            resourceLocation="https://graph.microsoft.com/beta/.../numberAssignments/123",
            status=OperationStatus.SUCCEEDED,
        )
        assert (
            op_num.resource_location
            == "https://graph.microsoft.com/beta/.../numberAssignments/123"
        )
        assert op_num.status == OperationStatus.SUCCEEDED

    @pytest.mark.unit
    def test_create_from_snake_case(self) -> None:
        """OperationNumber can be created with snake_case field names (populate_by_name)."""
        op_num = OperationNumber.model_validate(
            {
                "resource_location": "https://graph.microsoft.com/beta/.../numberAssignments/123",
                "status": "succeeded",
            }
        )
        assert (
            op_num.resource_location
            == "https://graph.microsoft.com/beta/.../numberAssignments/123"
        )
        assert op_num.status == OperationStatus.SUCCEEDED

    @pytest.mark.unit
    def test_status_from_string(self) -> None:
        """OperationNumber parses status from string."""
        op_num = OperationNumber(
            resourceLocation="https://example.com",
            status="running",  # type: ignore[arg-type]
        )
        assert op_num.status == OperationStatus.RUNNING

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """OperationNumber rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            OperationNumber(
                resourceLocation="https://example.com",
                status=OperationStatus.SUCCEEDED,
                extra_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_invalid_status_rejected(self) -> None:
        """OperationNumber rejects invalid status value."""
        with pytest.raises(ValidationError) as exc_info:
            OperationNumber(
                resourceLocation="https://example.com",
                status="invalid-status",  # type: ignore[arg-type]
            )
        assert "status" in str(exc_info.value).lower()


class TestAsyncOperation:
    """Tests for AsyncOperation model."""

    @pytest.mark.unit
    def test_create_minimal(self) -> None:
        """AsyncOperation can be created with required fields only."""
        op = AsyncOperation(
            id="op-123",
            createdDateTime="2025-02-03T22:03:26Z",  # type: ignore[arg-type]
            status=OperationStatus.RUNNING,
        )
        assert op.id == "op-123"
        assert op.status == OperationStatus.RUNNING
        assert op.odata_type is None
        assert op.numbers == []

    @pytest.mark.unit
    def test_create_full(self) -> None:
        """AsyncOperation can be created with all fields."""
        op = AsyncOperation(
            **{
                "@odata.type": "#microsoft.graph.teamsAdministration.telephoneNumberLongRunningOperation",
            },
            id="QXNzaWdubWVudHw2Y2E4Yjc0Ni0...",
            createdDateTime="2025-02-03T22:03:26Z",  # type: ignore[arg-type]
            status=OperationStatus.SUCCEEDED,
            numbers=[
                OperationNumber(
                    resourceLocation="https://graph.microsoft.com/beta/.../numberAssignments/123",
                    status=OperationStatus.SUCCEEDED,
                )
            ],
        )
        assert (
            op.odata_type
            == "#microsoft.graph.teamsAdministration.telephoneNumberLongRunningOperation"
        )
        assert op.id == "QXNzaWdubWVudHw2Y2E4Yjc0Ni0..."
        assert len(op.numbers) == 1
        assert op.numbers[0].status == OperationStatus.SUCCEEDED

    @pytest.mark.unit
    def test_create_from_graph_api_response(self) -> None:
        """AsyncOperation can be created from Graph API JSON response."""
        api_response = {
            "@odata.type": "#microsoft.graph.teamsAdministration.telephoneNumberLongRunningOperation",
            "id": "QXNzaWdubWVudHw2Y2E4Yjc0Ni0...",
            "createdDateTime": "2025-02-03T22:03:26Z",
            "status": "succeeded",
            "numbers": [
                {
                    "resourceLocation": "https://graph.microsoft.com/beta/.../numberAssignments/{id}",
                    "status": "succeeded",
                }
            ],
        }
        op = AsyncOperation.model_validate(api_response)
        assert op.id == "QXNzaWdubWVudHw2Y2E4Yjc0Ni0..."
        assert op.status == OperationStatus.SUCCEEDED
        assert op.created_date_time.year == 2025
        assert len(op.numbers) == 1

    @pytest.mark.unit
    def test_datetime_parsing(self) -> None:
        """AsyncOperation parses ISO 8601 datetime strings."""
        op = AsyncOperation(
            id="op-123",
            createdDateTime="2025-02-03T22:03:26Z",  # type: ignore[arg-type]
            status=OperationStatus.RUNNING,
        )
        assert op.created_date_time.year == 2025
        assert op.created_date_time.month == 2
        assert op.created_date_time.day == 3
        assert op.created_date_time.hour == 22
        assert op.created_date_time.minute == 3

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """AsyncOperation rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            AsyncOperation(
                id="op-123",
                createdDateTime="2025-02-03T22:03:26Z",  # type: ignore[arg-type]
                status=OperationStatus.RUNNING,
                extra_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_nested_extra_field_rejected(self) -> None:
        """AsyncOperation rejects extra fields in nested OperationNumber."""
        with pytest.raises(ValidationError) as exc_info:
            AsyncOperation.model_validate(
                {
                    "id": "op-123",
                    "createdDateTime": "2025-02-03T22:03:26Z",
                    "status": "succeeded",
                    "numbers": [
                        {
                            "resourceLocation": "https://example.com",
                            "status": "succeeded",
                            "extraField": "should fail",
                        }
                    ],
                }
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_all_operation_statuses(self) -> None:
        """AsyncOperation accepts all valid operation statuses."""
        for status in OperationStatus:
            op = AsyncOperation(
                id="op-123",
                createdDateTime="2025-02-03T22:03:26Z",  # type: ignore[arg-type]
                status=status,
            )
            assert op.status == status


class TestNumberAssignment:
    """Tests for NumberAssignment model."""

    @pytest.mark.unit
    def test_create_minimal(self) -> None:
        """NumberAssignment can be created with required fields only."""
        num = NumberAssignment(
            id="num-123",
            telephoneNumber="+12052582895",
            numberType=NumberType.CALLING_PLAN,
            activationState=ActivationState.ACTIVATED,
            capabilities=["userAssignment"],
            assignmentStatus=AssignmentStatus.UNASSIGNED,
        )
        assert num.id == "num-123"
        assert num.telephone_number == "+12052582895"
        assert num.number_type == NumberType.CALLING_PLAN
        assert num.activation_state == ActivationState.ACTIVATED
        assert num.capabilities == ["userAssignment"]
        assert num.assignment_status == AssignmentStatus.UNASSIGNED
        # Check defaults
        assert num.operator_id is None
        assert num.location_id is None
        assert num.assignment_category is None
        assert num.city is None
        assert num.supported_customer_actions == []

    @pytest.mark.unit
    def test_create_full(self) -> None:
        """NumberAssignment can be created with all fields."""
        num = NumberAssignment(
            id="num-123",
            telephoneNumber="+12052582895",
            operatorId="op-uuid-123",
            numberType=NumberType.OPERATOR_CONNECT,
            activationState=ActivationState.ACTIVATED,
            capabilities=["userAssignment", "conferenceAssignment"],
            locationId="loc-123",
            civicAddressId="civic-123",
            networkSiteId="site-123",
            assignmentTargetId="user-uuid-456",
            assignmentCategory=AssignmentCategory.PRIMARY,
            assignmentStatus=AssignmentStatus.USER_ASSIGNED,
            portInStatus="completed",
            isoCountryCode="US",
            city="Seattle",
            numberSource="online",
            supportedCustomerActions=["locationUpdate"],
            reverseNumberLookupOptions=["carrier"],
        )
        assert num.operator_id == "op-uuid-123"
        assert num.number_type == NumberType.OPERATOR_CONNECT
        assert num.location_id == "loc-123"
        assert num.assignment_target_id == "user-uuid-456"
        assert num.assignment_category == AssignmentCategory.PRIMARY
        assert num.assignment_status == AssignmentStatus.USER_ASSIGNED
        assert num.city == "Seattle"
        assert num.supported_customer_actions == ["locationUpdate"]

    @pytest.mark.unit
    def test_create_from_graph_api_response(self) -> None:
        """NumberAssignment can be created from Graph API JSON response."""
        api_response = {
            "id": "num-abc-123",
            "telephoneNumber": "+12052582895",
            "operatorId": "op-uuid-123",
            "numberType": "directRouting",
            "activationState": "activated",
            "capabilities": ["userAssignment", "voiceApplicationAssignment"],
            "locationId": "loc-456",
            "civicAddressId": None,
            "networkSiteId": None,
            "assignmentTargetId": "user-uuid-789",
            "assignmentCategory": "primary",
            "assignmentStatus": "userAssigned",
            "portInStatus": "completed",
            "isoCountryCode": "US",
            "city": "Birmingham",
            "numberSource": "online",
            "supportedCustomerActions": ["locationUpdate"],
            "reverseNumberLookupOptions": [],
        }
        num = NumberAssignment.model_validate(api_response)
        assert num.id == "num-abc-123"
        assert num.telephone_number == "+12052582895"
        assert num.number_type == NumberType.DIRECT_ROUTING
        assert num.activation_state == ActivationState.ACTIVATED
        assert num.assignment_category == AssignmentCategory.PRIMARY
        assert num.assignment_status == AssignmentStatus.USER_ASSIGNED
        assert num.city == "Birmingham"

    @pytest.mark.unit
    def test_create_from_snake_case(self) -> None:
        """NumberAssignment can be created with snake_case field names (populate_by_name)."""
        num = NumberAssignment.model_validate(
            {
                "id": "num-123",
                "telephone_number": "+12052582895",
                "number_type": "callingPlan",
                "activation_state": "activated",
                "capabilities": ["userAssignment"],
                "assignment_status": "unassigned",
            }
        )
        assert num.telephone_number == "+12052582895"
        assert num.number_type == NumberType.CALLING_PLAN

    @pytest.mark.unit
    def test_null_optional_fields(self) -> None:
        """NumberAssignment handles null optional fields correctly."""
        api_response: dict[str, object] = {
            "id": "num-123",
            "telephoneNumber": "+12052582895",
            "operatorId": None,
            "numberType": "callingPlan",
            "activationState": "activated",
            "capabilities": [],
            "locationId": None,
            "civicAddressId": None,
            "networkSiteId": None,
            "assignmentTargetId": None,
            "assignmentCategory": None,
            "assignmentStatus": "unassigned",
            "portInStatus": None,
            "isoCountryCode": None,
            "city": None,
            "numberSource": None,
            "supportedCustomerActions": [],
            "reverseNumberLookupOptions": [],
        }
        num = NumberAssignment.model_validate(api_response)
        assert num.operator_id is None
        assert num.location_id is None
        assert num.assignment_category is None
        assert num.city is None

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """NumberAssignment rejects unknown fields (extra='forbid')."""
        with pytest.raises(ValidationError) as exc_info:
            NumberAssignment(
                id="num-123",
                telephoneNumber="+12052582895",
                numberType=NumberType.CALLING_PLAN,
                activationState=ActivationState.ACTIVATED,
                capabilities=[],
                assignmentStatus=AssignmentStatus.UNASSIGNED,
                unknown_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_invalid_number_type_rejected(self) -> None:
        """NumberAssignment rejects invalid number type."""
        with pytest.raises(ValidationError) as exc_info:
            NumberAssignment(
                id="num-123",
                telephoneNumber="+12052582895",
                numberType="invalidType",  # type: ignore[arg-type]
                activationState=ActivationState.ACTIVATED,
                capabilities=[],
                assignmentStatus=AssignmentStatus.UNASSIGNED,
            )
        assert "numbertype" in str(exc_info.value).lower()

    @pytest.mark.unit
    def test_invalid_activation_state_rejected(self) -> None:
        """NumberAssignment rejects invalid activation state."""
        with pytest.raises(ValidationError) as exc_info:
            NumberAssignment(
                id="num-123",
                telephoneNumber="+12052582895",
                numberType=NumberType.CALLING_PLAN,
                activationState="invalidState",  # type: ignore[arg-type]
                capabilities=[],
                assignmentStatus=AssignmentStatus.UNASSIGNED,
            )
        assert "activationstate" in str(exc_info.value).lower()

    @pytest.mark.unit
    def test_invalid_assignment_status_rejected(self) -> None:
        """NumberAssignment rejects invalid assignment status."""
        with pytest.raises(ValidationError) as exc_info:
            NumberAssignment(
                id="num-123",
                telephoneNumber="+12052582895",
                numberType=NumberType.CALLING_PLAN,
                activationState=ActivationState.ACTIVATED,
                capabilities=[],
                assignmentStatus="invalidStatus",  # type: ignore[arg-type]
            )
        assert "assignmentstatus" in str(exc_info.value).lower()

    @pytest.mark.unit
    def test_invalid_assignment_category_rejected(self) -> None:
        """NumberAssignment rejects invalid assignment category."""
        with pytest.raises(ValidationError) as exc_info:
            NumberAssignment(
                id="num-123",
                telephoneNumber="+12052582895",
                numberType=NumberType.CALLING_PLAN,
                activationState=ActivationState.ACTIVATED,
                capabilities=[],
                assignmentStatus=AssignmentStatus.UNASSIGNED,
                assignmentCategory="invalidCategory",  # type: ignore[arg-type]
            )
        assert "assignmentcategory" in str(exc_info.value).lower()

    @pytest.mark.unit
    def test_serialization(self) -> None:
        """NumberAssignment serializes to dict correctly."""
        num = NumberAssignment(
            id="num-123",
            telephoneNumber="+12052582895",
            numberType=NumberType.CALLING_PLAN,
            activationState=ActivationState.ACTIVATED,
            capabilities=["userAssignment"],
            assignmentStatus=AssignmentStatus.UNASSIGNED,
        )
        data = num.model_dump()
        assert data["id"] == "num-123"
        assert data["telephone_number"] == "+12052582895"
        assert data["number_type"] == NumberType.CALLING_PLAN
        assert data["capabilities"] == ["userAssignment"]

    @pytest.mark.unit
    def test_serialization_by_alias(self) -> None:
        """NumberAssignment serializes with camelCase aliases."""
        num = NumberAssignment(
            id="num-123",
            telephoneNumber="+12052582895",
            numberType=NumberType.CALLING_PLAN,
            activationState=ActivationState.ACTIVATED,
            capabilities=["userAssignment"],
            assignmentStatus=AssignmentStatus.UNASSIGNED,
        )
        data = num.model_dump(by_alias=True)
        assert "telephoneNumber" in data
        assert "numberType" in data
        assert "activationState" in data
        assert "assignmentStatus" in data
        assert data["telephoneNumber"] == "+12052582895"

    @pytest.mark.unit
    def test_all_number_types(self) -> None:
        """NumberAssignment accepts all valid number types."""
        for number_type in NumberType:
            num = NumberAssignment(
                id="num-123",
                telephoneNumber="+12052582895",
                numberType=number_type,
                activationState=ActivationState.ACTIVATED,
                capabilities=[],
                assignmentStatus=AssignmentStatus.UNASSIGNED,
            )
            assert num.number_type == number_type

    @pytest.mark.unit
    def test_all_activation_states(self) -> None:
        """NumberAssignment accepts all valid activation states."""
        for state in ActivationState:
            num = NumberAssignment(
                id="num-123",
                telephoneNumber="+12052582895",
                numberType=NumberType.CALLING_PLAN,
                activationState=state,
                capabilities=[],
                assignmentStatus=AssignmentStatus.UNASSIGNED,
            )
            assert num.activation_state == state

    @pytest.mark.unit
    def test_all_assignment_statuses(self) -> None:
        """NumberAssignment accepts all valid assignment statuses."""
        for status in AssignmentStatus:
            num = NumberAssignment(
                id="num-123",
                telephoneNumber="+12052582895",
                numberType=NumberType.CALLING_PLAN,
                activationState=ActivationState.ACTIVATED,
                capabilities=[],
                assignmentStatus=status,
            )
            assert num.assignment_status == status

    @pytest.mark.unit
    def test_all_assignment_categories(self) -> None:
        """NumberAssignment accepts all valid assignment categories."""
        for category in AssignmentCategory:
            num = NumberAssignment(
                id="num-123",
                telephoneNumber="+12052582895",
                numberType=NumberType.CALLING_PLAN,
                activationState=ActivationState.ACTIVATED,
                capabilities=[],
                assignmentStatus=AssignmentStatus.UNASSIGNED,
                assignmentCategory=category,
            )
            assert num.assignment_category == category


class TestPolicy:
    """Tests for Policy model (CSV cache data)."""

    @pytest.mark.unit
    def test_create_from_camel_case(self) -> None:
        """Policy can be created with camelCase aliases (CSV column names)."""
        policy = Policy(
            policyType="TeamsCallingPolicy",
            policyName="Global",
            policyId="Global",
            description="Default calling policy",
            isGlobal=True,
        )
        assert policy.policy_type == "TeamsCallingPolicy"
        assert policy.policy_name == "Global"
        assert policy.policy_id == "Global"
        assert policy.description == "Default calling policy"
        assert policy.is_global is True

    @pytest.mark.unit
    def test_create_from_snake_case(self) -> None:
        """Policy can be created with snake_case field names (populate_by_name)."""
        policy = Policy.model_validate(
            {
                "policy_type": "TenantDialPlan",
                "policy_name": "US-DialPlan",
                "policy_id": "US-DialPlan",
                "description": "US dialing rules",
                "is_global": False,
            }
        )
        assert policy.policy_type == "TenantDialPlan"
        assert policy.policy_name == "US-DialPlan"
        assert policy.is_global is False

    @pytest.mark.unit
    def test_create_with_null_description(self) -> None:
        """Policy can be created with null description."""
        policy = Policy(
            policyType="TeamsVoicemailPolicy",
            policyName="Custom",
            policyId="Custom",
            description=None,
            isGlobal=False,
        )
        assert policy.description is None

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """Policy rejects unknown fields (extra='forbid')."""
        with pytest.raises(ValidationError) as exc_info:
            Policy(
                policyType="TeamsCallingPolicy",
                policyName="Global",
                policyId="Global",
                description=None,
                isGlobal=True,
                extra_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_missing_required_field_rejected(self) -> None:
        """Policy requires all fields except description."""
        with pytest.raises(ValidationError):
            Policy(
                policyType="TeamsCallingPolicy",
                policyName="Global",
                # Missing policyId, isGlobal
            )  # type: ignore[call-arg]

    @pytest.mark.unit
    def test_serialization(self) -> None:
        """Policy serializes to dict correctly."""
        policy = Policy(
            policyType="TeamsCallingPolicy",
            policyName="Global",
            policyId="Global",
            description="Test",
            isGlobal=True,
        )
        data = policy.model_dump()
        assert data["policy_type"] == "TeamsCallingPolicy"
        assert data["policy_name"] == "Global"
        assert data["is_global"] is True

    @pytest.mark.unit
    def test_serialization_by_alias(self) -> None:
        """Policy serializes with camelCase aliases."""
        policy = Policy(
            policyType="TeamsCallingPolicy",
            policyName="Global",
            policyId="Global",
            description=None,
            isGlobal=True,
        )
        data = policy.model_dump(by_alias=True)
        assert "policyType" in data
        assert "policyName" in data
        assert "isGlobal" in data


class TestEmergencyLocation:
    """Tests for EmergencyLocation model (CSV cache data)."""

    @pytest.mark.unit
    def test_create_from_camel_case(self) -> None:
        """EmergencyLocation can be created with camelCase aliases."""
        location = EmergencyLocation(
            locationId="loc-uuid-123",
            civicAddressId="civic-uuid-456",
            description="Main Office",
            companyName="Contoso Corp",
            address="123 Main St",
            city="Seattle",
            stateOrProvince="WA",
            postalCode="98101",
            countryOrRegion="US",
            isDefault=True,
        )
        assert location.location_id == "loc-uuid-123"
        assert location.civic_address_id == "civic-uuid-456"
        assert location.description == "Main Office"
        assert location.company_name == "Contoso Corp"
        assert location.address == "123 Main St"
        assert location.city == "Seattle"
        assert location.state_or_province == "WA"
        assert location.postal_code == "98101"
        assert location.country_or_region == "US"
        assert location.is_default is True

    @pytest.mark.unit
    def test_create_from_snake_case(self) -> None:
        """EmergencyLocation can be created with snake_case field names."""
        location = EmergencyLocation.model_validate(
            {
                "location_id": "loc-123",
                "civic_address_id": "civic-456",
                "description": "Branch Office",
                "company_name": "Contoso",
                "address": "456 Oak Ave",
                "city": "Portland",
                "state_or_province": "OR",
                "postal_code": "97201",
                "country_or_region": "US",
                "is_default": False,
            }
        )
        assert location.location_id == "loc-123"
        assert location.city == "Portland"
        assert location.is_default is False

    @pytest.mark.unit
    def test_create_with_null_optional_fields(self) -> None:
        """EmergencyLocation can have null optional fields."""
        location = EmergencyLocation(
            locationId="loc-123",
            civicAddressId="civic-456",
            description="Remote Location",
            companyName=None,
            address=None,
            city=None,
            stateOrProvince=None,
            postalCode=None,
            countryOrRegion=None,
            isDefault=False,
        )
        assert location.company_name is None
        assert location.address is None
        assert location.city is None
        assert location.state_or_province is None
        assert location.postal_code is None
        assert location.country_or_region is None

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """EmergencyLocation rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            EmergencyLocation(
                locationId="loc-123",
                civicAddressId="civic-456",
                description="Test",
                companyName=None,
                address=None,
                city=None,
                stateOrProvince=None,
                postalCode=None,
                countryOrRegion=None,
                isDefault=False,
                extra_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_missing_required_field_rejected(self) -> None:
        """EmergencyLocation requires essential fields."""
        with pytest.raises(ValidationError):
            EmergencyLocation(
                locationId="loc-123",
                # Missing civicAddressId, description, isDefault
            )  # type: ignore[call-arg]

    @pytest.mark.unit
    def test_serialization(self) -> None:
        """EmergencyLocation serializes to dict correctly."""
        location = EmergencyLocation(
            locationId="loc-123",
            civicAddressId="civic-456",
            description="Test Location",
            companyName="Test Co",
            address="123 Test St",
            city="TestCity",
            stateOrProvince="TS",
            postalCode="12345",
            countryOrRegion="US",
            isDefault=True,
        )
        data = location.model_dump()
        assert data["location_id"] == "loc-123"
        assert data["civic_address_id"] == "civic-456"
        assert data["is_default"] is True

    @pytest.mark.unit
    def test_serialization_by_alias(self) -> None:
        """EmergencyLocation serializes with camelCase aliases."""
        location = EmergencyLocation(
            locationId="loc-123",
            civicAddressId="civic-456",
            description="Test",
            companyName=None,
            address=None,
            city=None,
            stateOrProvince=None,
            postalCode=None,
            countryOrRegion=None,
            isDefault=False,
        )
        data = location.model_dump(by_alias=True)
        assert "locationId" in data
        assert "civicAddressId" in data
        assert "isDefault" in data
        assert "stateOrProvince" in data


class TestGraphErrorDetail:
    """Tests for GraphErrorDetail model."""

    @pytest.mark.unit
    def test_create_minimal(self) -> None:
        """GraphErrorDetail can be created with required fields only."""
        error = GraphErrorDetail(
            code="BadRequest",
            message="The request is invalid.",
        )
        assert error.code == "BadRequest"
        assert error.message == "The request is invalid."
        assert error.target is None
        assert error.details is None

    @pytest.mark.unit
    def test_create_with_target(self) -> None:
        """GraphErrorDetail can include target field."""
        error = GraphErrorDetail(
            code="InvalidField",
            message="Field value is invalid.",
            target="telephoneNumber",
        )
        assert error.target == "telephoneNumber"

    @pytest.mark.unit
    def test_create_with_nested_details(self) -> None:
        """GraphErrorDetail can have nested error details."""
        error = GraphErrorDetail(
            code="ValidationError",
            message="Multiple validation errors occurred.",
            details=[
                GraphErrorDetail(
                    code="InvalidFormat",
                    message="Phone number format is invalid.",
                    target="telephoneNumber",
                ),
                GraphErrorDetail(
                    code="Required",
                    message="User ID is required.",
                    target="userId",
                ),
            ],
        )
        assert len(error.details) == 2
        assert error.details[0].code == "InvalidFormat"
        assert error.details[1].target == "userId"

    @pytest.mark.unit
    def test_create_from_dict(self) -> None:
        """GraphErrorDetail can be created from dict with nested details."""
        error = GraphErrorDetail.model_validate(
            {
                "code": "BadRequest",
                "message": "Request failed.",
                "target": None,
                "details": [
                    {
                        "code": "InnerError",
                        "message": "Nested error.",
                    }
                ],
            }
        )
        assert error.code == "BadRequest"
        assert error.details is not None
        assert len(error.details) == 1
        assert error.details[0].code == "InnerError"

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """GraphErrorDetail rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            GraphErrorDetail(
                code="BadRequest",
                message="Error",
                extra_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_nested_extra_field_rejected(self) -> None:
        """GraphErrorDetail rejects extra fields in nested details."""
        with pytest.raises(ValidationError) as exc_info:
            GraphErrorDetail.model_validate(
                {
                    "code": "BadRequest",
                    "message": "Error",
                    "details": [
                        {
                            "code": "Inner",
                            "message": "Inner error",
                            "extraField": "should fail",
                        }
                    ],
                }
            )
        assert "extra_forbidden" in str(exc_info.value)


class TestGraphErrorResponse:
    """Tests for GraphErrorResponse model."""

    @pytest.mark.unit
    def test_create_simple(self) -> None:
        """GraphErrorResponse can wrap a simple error."""
        response = GraphErrorResponse(
            error=GraphErrorDetail(
                code="ResourceNotFound",
                message="The specified resource was not found.",
            )
        )
        assert response.error.code == "ResourceNotFound"
        assert response.error.message == "The specified resource was not found."

    @pytest.mark.unit
    def test_create_from_dict(self) -> None:
        """GraphErrorResponse can be created from dict (typical API response)."""
        api_error = {
            "error": {
                "code": "Authorization_RequestDenied",
                "message": "Insufficient privileges to complete the operation.",
                "target": None,
                "details": None,
            }
        }
        response = GraphErrorResponse.model_validate(api_error)
        assert response.error.code == "Authorization_RequestDenied"

    @pytest.mark.unit
    def test_create_with_nested_details(self) -> None:
        """GraphErrorResponse can contain errors with nested details."""
        api_error = {
            "error": {
                "code": "BadRequest",
                "message": "Request validation failed.",
                "details": [
                    {
                        "code": "InvalidFormat",
                        "message": "Phone number must be in E.164 format.",
                        "target": "telephoneNumber",
                    },
                    {
                        "code": "InvalidValue",
                        "message": "Location ID not found.",
                        "target": "locationId",
                    },
                ],
            }
        }
        response = GraphErrorResponse.model_validate(api_error)
        assert response.error.code == "BadRequest"
        assert response.error.details is not None
        assert len(response.error.details) == 2
        assert response.error.details[0].target == "telephoneNumber"

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """GraphErrorResponse rejects unknown fields at top level."""
        with pytest.raises(ValidationError) as exc_info:
            GraphErrorResponse(
                error=GraphErrorDetail(code="Error", message="Test"),
                extra_field="should fail",  # type: ignore[call-arg]
            )
        assert "extra_forbidden" in str(exc_info.value)


class TestGraphListResponse:
    """Tests for GraphListResponse generic model."""

    @pytest.mark.unit
    def test_create_with_user_configuration(self) -> None:
        """GraphListResponse can wrap UserConfiguration items."""
        response = GraphListResponse[UserConfiguration].model_validate(
            {
                "@odata.context": "https://graph.microsoft.com/beta/$metadata#teamwork/teamsPhoneConfiguration/users",
                "@odata.count": 2,
                "@odata.nextLink": "https://graph.microsoft.com/beta/teamwork/teamsPhoneConfiguration/users?$skiptoken=abc123",
                "value": [
                    {
                        "id": "user-1",
                        "userPrincipalName": "user1@contoso.com",
                        "tenantId": "tenant-123",
                        "accountType": "user",
                        "isEnterpriseVoiceEnabled": True,
                    },
                    {
                        "id": "user-2",
                        "userPrincipalName": "user2@contoso.com",
                        "tenantId": "tenant-123",
                        "accountType": "user",
                        "isEnterpriseVoiceEnabled": False,
                    },
                ],
            }
        )
        assert (
            response.odata_context
            == "https://graph.microsoft.com/beta/$metadata#teamwork/teamsPhoneConfiguration/users"
        )
        assert response.odata_count == 2
        assert response.odata_next_link is not None
        assert "skiptoken" in response.odata_next_link
        assert len(response.value) == 2
        assert response.value[0].user_principal_name == "user1@contoso.com"
        assert response.value[1].is_enterprise_voice_enabled is False

    @pytest.mark.unit
    def test_create_with_number_assignment(self) -> None:
        """GraphListResponse can wrap NumberAssignment items."""
        response = GraphListResponse[NumberAssignment].model_validate(
            {
                "@odata.context": "https://graph.microsoft.com/beta/$metadata#telephoneNumbers",
                "value": [
                    {
                        "id": "num-1",
                        "telephoneNumber": "+12055551234",
                        "numberType": "callingPlan",
                        "activationState": "activated",
                        "capabilities": ["userAssignment"],
                        "assignmentStatus": "unassigned",
                    }
                ],
            }
        )
        assert len(response.value) == 1
        assert response.value[0].telephone_number == "+12055551234"
        assert response.value[0].number_type == NumberType.CALLING_PLAN

    @pytest.mark.unit
    def test_create_empty_value_list(self) -> None:
        """GraphListResponse handles empty value list."""
        response = GraphListResponse[UserConfiguration].model_validate(
            {
                "@odata.context": "https://graph.microsoft.com/beta/$metadata#...",
                "@odata.count": 0,
                "value": [],
            }
        )
        assert len(response.value) == 0
        assert response.odata_count == 0

    @pytest.mark.unit
    def test_create_minimal(self) -> None:
        """GraphListResponse can be created with just value field."""
        response = GraphListResponse[UserConfiguration].model_validate(
            {
                "value": [
                    {
                        "id": "user-1",
                        "userPrincipalName": "user@contoso.com",
                        "tenantId": "tenant-123",
                        "accountType": "user",
                        "isEnterpriseVoiceEnabled": True,
                    }
                ],
            }
        )
        assert response.odata_context is None
        assert response.odata_count is None
        assert response.odata_next_link is None
        assert len(response.value) == 1

    @pytest.mark.unit
    def test_odata_aliases_work(self) -> None:
        """GraphListResponse correctly maps @odata.* aliases."""
        response = GraphListResponse[UserConfiguration](
            **{
                "@odata.context": "https://example.com/context",
                "@odata.count": 5,
                "@odata.nextLink": "https://example.com/next",
            },
            value=[],
        )
        assert response.odata_context == "https://example.com/context"
        assert response.odata_count == 5
        assert response.odata_next_link == "https://example.com/next"

    @pytest.mark.unit
    def test_extra_field_rejected(self) -> None:
        """GraphListResponse rejects unknown fields."""
        with pytest.raises(ValidationError) as exc_info:
            GraphListResponse[UserConfiguration].model_validate(
                {
                    "value": [],
                    "extraField": "should fail",
                }
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_nested_extra_field_rejected(self) -> None:
        """GraphListResponse rejects extra fields in nested items."""
        with pytest.raises(ValidationError) as exc_info:
            GraphListResponse[UserConfiguration].model_validate(
                {
                    "value": [
                        {
                            "id": "user-1",
                            "userPrincipalName": "user@contoso.com",
                            "tenantId": "tenant-123",
                            "accountType": "user",
                            "isEnterpriseVoiceEnabled": True,
                            "extraField": "should fail",
                        }
                    ],
                }
            )
        assert "extra_forbidden" in str(exc_info.value)

    @pytest.mark.unit
    def test_serialization(self) -> None:
        """GraphListResponse serializes to dict correctly."""
        response = GraphListResponse[UserConfiguration].model_validate(
            {
                "@odata.context": "https://example.com",
                "@odata.count": 1,
                "value": [
                    {
                        "id": "user-1",
                        "userPrincipalName": "user@contoso.com",
                        "tenantId": "tenant-123",
                        "accountType": "user",
                        "isEnterpriseVoiceEnabled": True,
                    }
                ],
            }
        )
        data = response.model_dump()
        assert data["odata_context"] == "https://example.com"
        assert data["odata_count"] == 1
        assert len(data["value"]) == 1

    @pytest.mark.unit
    def test_serialization_by_alias(self) -> None:
        """GraphListResponse serializes with @odata.* aliases."""
        response = GraphListResponse[UserConfiguration].model_validate(
            {
                "@odata.context": "https://example.com",
                "@odata.count": 1,
                "@odata.nextLink": "https://example.com/next",
                "value": [],
            }
        )
        data = response.model_dump(by_alias=True)
        assert "@odata.context" in data
        assert "@odata.count" in data
        assert "@odata.nextLink" in data
