"""Contract tests for phone number-related Pydantic models.

These tests validate that NumberAssignment and related enum models correctly parse
live Graph API responses from /admin/teams/telephoneNumberManagement/numberAssignments.

Uses extra='forbid' validation to detect API contract drift - any unexpected field
in the API response will cause a ValidationError.
"""

from __future__ import annotations

from typing import Any

import pytest

from teams_phone.infrastructure.graph_client import GraphClient
from teams_phone.models import (
    ActivationState,
    AssignmentCategory,
    AssignmentStatus,
    NumberAssignment,
    NumberType,
)


def _strip_odata_metadata(data: dict[str, Any]) -> dict[str, Any]:
    """Strip OData metadata fields from a response dict.

    Graph API responses include @odata.context and other @ prefixed fields
    that are not part of the actual data contract. This helper removes them
    before Pydantic validation with extra="forbid".
    """
    return {k: v for k, v in data.items() if not k.startswith("@")}


@pytest.mark.contract
@pytest.mark.asyncio
async def test_number_assignment_schema(live_graph_client: GraphClient) -> None:
    """Validate NumberAssignment model against live /numberAssignments endpoint.

    Fetches a small batch of phone numbers and validates each against the
    NumberAssignment Pydantic model. Uses extra='forbid' to catch any unexpected
    fields from the API.
    """
    data = await live_graph_client.get(
        "/admin/teams/telephoneNumberManagement/numberAssignments?$top=5"
    )
    numbers = data.get("value", [])

    if not numbers:
        pytest.skip("No phone numbers available in test tenant inventory")

    # Validate each number assignment
    validated_numbers = []
    for number_data in numbers:
        # Strip OData metadata before validation
        clean_data = _strip_odata_metadata(number_data)
        number = NumberAssignment.model_validate(clean_data)
        validated_numbers.append(number)

    # Basic sanity checks on validated data
    assert len(validated_numbers) == len(numbers)
    for number in validated_numbers:
        assert number.id  # ID should be present
        assert number.telephone_number  # Phone number should be present
        assert number.number_type  # Number type should be present
        assert number.activation_state  # Activation state should be present
        assert number.assignment_status  # Assignment status should be present


@pytest.mark.contract
@pytest.mark.asyncio
async def test_number_type_enum_values(live_graph_client: GraphClient) -> None:
    """Verify NumberType enum covers all API values in live data.

    Fetches phone numbers and validates that all number_type values from the API
    are covered by the NumberType enum. This catches cases where the API returns
    new enum values not yet defined in the model.
    """
    data = await live_graph_client.get(
        "/admin/teams/telephoneNumberManagement/numberAssignments?$top=50"
    )
    numbers = data.get("value", [])

    if not numbers:
        pytest.skip("No phone numbers available in test tenant inventory")

    # Collect all unique number types from the API
    api_number_types: set[str] = set()
    for number_data in numbers:
        if "numberType" in number_data:
            api_number_types.add(number_data["numberType"])

    # Verify each API value is covered by the enum
    enum_values = {e.value for e in NumberType}
    for api_value in api_number_types:
        assert api_value in enum_values, (
            f"NumberType enum missing value: '{api_value}'. Known values: {enum_values}"
        )


@pytest.mark.contract
@pytest.mark.asyncio
async def test_activation_state_enum_values(live_graph_client: GraphClient) -> None:
    """Verify ActivationState enum covers all API values in live data.

    Fetches phone numbers and validates that all activation_state values from the API
    are covered by the ActivationState enum.
    """
    data = await live_graph_client.get(
        "/admin/teams/telephoneNumberManagement/numberAssignments?$top=50"
    )
    numbers = data.get("value", [])

    if not numbers:
        pytest.skip("No phone numbers available in test tenant inventory")

    # Collect all unique activation states from the API
    api_activation_states: set[str] = set()
    for number_data in numbers:
        if "activationState" in number_data:
            api_activation_states.add(number_data["activationState"])

    # Verify each API value is covered by the enum
    enum_values = {e.value for e in ActivationState}
    for api_value in api_activation_states:
        assert api_value in enum_values, (
            f"ActivationState enum missing value: '{api_value}'. "
            f"Known values: {enum_values}"
        )


@pytest.mark.contract
@pytest.mark.asyncio
async def test_assignment_status_enum_values(live_graph_client: GraphClient) -> None:
    """Verify AssignmentStatus enum covers all API values in live data.

    Fetches phone numbers and validates that all assignment_status values from the API
    are covered by the AssignmentStatus enum.
    """
    data = await live_graph_client.get(
        "/admin/teams/telephoneNumberManagement/numberAssignments?$top=50"
    )
    numbers = data.get("value", [])

    if not numbers:
        pytest.skip("No phone numbers available in test tenant inventory")

    # Collect all unique assignment statuses from the API
    api_assignment_statuses: set[str] = set()
    for number_data in numbers:
        if "assignmentStatus" in number_data:
            api_assignment_statuses.add(number_data["assignmentStatus"])

    # Verify each API value is covered by the enum
    enum_values = {e.value for e in AssignmentStatus}
    for api_value in api_assignment_statuses:
        assert api_value in enum_values, (
            f"AssignmentStatus enum missing value: '{api_value}'. "
            f"Known values: {enum_values}"
        )


@pytest.mark.contract
@pytest.mark.asyncio
async def test_assignment_category_enum_values(live_graph_client: GraphClient) -> None:
    """Verify AssignmentCategory enum covers all API values in live data.

    Fetches phone numbers and validates that all assignment_category values from the API
    are covered by the AssignmentCategory enum.
    """
    data = await live_graph_client.get(
        "/admin/teams/telephoneNumberManagement/numberAssignments?$top=50"
    )
    numbers = data.get("value", [])

    if not numbers:
        pytest.skip("No phone numbers available in test tenant inventory")

    # Collect all unique assignment categories from the API (may be null)
    api_assignment_categories: set[str] = set()
    for number_data in numbers:
        category = number_data.get("assignmentCategory")
        if category is not None:
            api_assignment_categories.add(category)

    if not api_assignment_categories:
        pytest.skip("No assignment categories found in test tenant data")

    # Verify each API value is covered by the enum
    enum_values = {e.value for e in AssignmentCategory}
    for api_value in api_assignment_categories:
        assert api_value in enum_values, (
            f"AssignmentCategory enum missing value: '{api_value}'. "
            f"Known values: {enum_values}"
        )


@pytest.mark.contract
@pytest.mark.asyncio
async def test_number_with_location(live_graph_client: GraphClient) -> None:
    """Validate phone numbers with location data.

    Fetches phone numbers and validates that those with location_id have
    properly formatted location-related fields.
    """
    data = await live_graph_client.get(
        "/admin/teams/telephoneNumberManagement/numberAssignments?$top=50"
    )
    numbers = data.get("value", [])

    if not numbers:
        pytest.skip("No phone numbers available in test tenant inventory")

    # Find numbers with location data
    numbers_with_location = []
    for number_data in numbers:
        clean_data = _strip_odata_metadata(number_data)
        number = NumberAssignment.model_validate(clean_data)
        if number.location_id:
            numbers_with_location.append(number)

    if not numbers_with_location:
        pytest.skip("No phone numbers with location data found in test tenant")

    # Validate location-related fields
    for number in numbers_with_location:
        assert number.location_id  # Should be present (we filtered for it)
        # civic_address_id is often present alongside location_id
        # but not guaranteed, so we just verify the model parsed correctly
