"""API contract validation command for Teams Phone CLI.

This module provides the api-check command for validating Pydantic models
against live Graph API responses to detect API contract drift.
"""

from __future__ import annotations

import asyncio
from dataclasses import dataclass
from typing import Any, Optional

import typer
from pydantic import ValidationError

from teams_phone.cli.context import get_context
from teams_phone.constants import ExitCode
from teams_phone.exceptions import TeamsPhoneError
from teams_phone.infrastructure import CacheManager, GraphClient
from teams_phone.models import NumberAssignment, UserConfiguration
from teams_phone.services import AuthService


@dataclass
class EndpointCheck:
    """Configuration for an endpoint contract check."""

    name: str
    endpoint: str
    model_name: str
    validator: Any  # Pydantic model class


@dataclass
class CheckResult:
    """Result of a single endpoint validation check."""

    endpoint_name: str
    passed: bool
    error_message: Optional[str] = None
    error_details: Optional[list[str]] = None


# Define endpoints to check
ENDPOINT_CHECKS = [
    EndpointCheck(
        name="userConfigurations",
        endpoint="/admin/teams/userConfigurations",
        model_name="UserConfiguration",
        validator=UserConfiguration,
    ),
    EndpointCheck(
        name="numberAssignments",
        endpoint="/admin/teams/telephoneNumberManagement/numberAssignments",
        model_name="NumberAssignment",
        validator=NumberAssignment,
    ),
]


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("@")}


async def _validate_endpoint(
    graph_client: GraphClient,
    check: EndpointCheck,
) -> CheckResult:
    """Validate a single endpoint against its Pydantic model.

    Args:
        graph_client: Authenticated Graph API client.
        check: Endpoint configuration to validate.

    Returns:
        CheckResult with pass/fail status and any error details.
    """
    try:
        # Fetch a single sample item from the endpoint
        response = await graph_client.get(check.endpoint, params={"$top": "1"})

        # Extract items from the list response
        items = response.get("value", [])

        if not items:
            # No data to validate - that's still a pass, just nothing to check
            return CheckResult(
                endpoint_name=check.name,
                passed=True,
                error_message="No data available for validation",
            )

        # Validate each item against the model
        for item in items:
            cleaned = _strip_odata_metadata(item)
            check.validator.model_validate(cleaned)

        return CheckResult(endpoint_name=check.name, passed=True)

    except ValidationError as e:
        # Extract error details for verbose output
        error_details = []
        for error in e.errors():
            loc = ".".join(str(part) for part in error["loc"])
            msg = error["msg"]
            error_type = error["type"]
            error_details.append(f"  - {loc}: {msg} (type: {error_type})")

        return CheckResult(
            endpoint_name=check.name,
            passed=False,
            error_message=f"Schema validation failed for {check.model_name}",
            error_details=error_details,
        )

    except TeamsPhoneError as e:
        # API errors (auth, rate limit, etc.)
        return CheckResult(
            endpoint_name=check.name,
            passed=False,
            error_message=f"API error: {e.message}",
        )


async def _run_validation(
    auth_service: AuthService,
    endpoint_filter: Optional[str],
) -> list[CheckResult]:
    """Run validation checks against Graph API endpoints.

    Args:
        auth_service: Authenticated service for API access.
        endpoint_filter: Optional endpoint name to check (None = all endpoints).

    Returns:
        List of CheckResult for each endpoint checked.
    """
    # Filter endpoints if specific one requested
    checks = ENDPOINT_CHECKS
    if endpoint_filter:
        checks = [c for c in ENDPOINT_CHECKS if c.name == endpoint_filter]

    results: list[CheckResult] = []

    async with GraphClient(auth_service) as graph_client:
        for check in checks:
            result = await _validate_endpoint(graph_client, check)
            results.append(result)

    return results


def api_check(
    ctx: typer.Context,
    endpoint: Optional[str] = typer.Option(
        None,
        "--endpoint",
        "-e",
        help="Check specific endpoint (userConfigurations, numberAssignments).",
    ),
    verbose: bool = typer.Option(
        False,
        "--verbose",
        "-V",
        help="Show detailed validation error output.",
    ),
) -> None:
    """Validate API contracts against live Graph API responses.

    Fetches sample data from Graph API endpoints and validates against
    Pydantic models with strict mode (extra="forbid") to detect API
    contract drift (new fields, renamed fields, removed fields).

    Exits with code 8 (CONTRACT_ERROR) if any validation failures.
    """
    cli_ctx = get_context(ctx)
    formatter = cli_ctx.get_output_formatter()

    try:
        config_manager = cli_ctx.get_config_manager()
        cache_manager = CacheManager()
        auth_service = AuthService(config_manager, cache_manager)

        # Validate endpoint filter if provided
        valid_endpoints = [c.name for c in ENDPOINT_CHECKS]
        if endpoint and endpoint not in valid_endpoints:
            formatter.error(
                f"Unknown endpoint: {endpoint}",
                remediation=f"Valid endpoints: {', '.join(valid_endpoints)}",
            )
            raise typer.Exit(ExitCode.VALIDATION_ERROR)

        # Run validation
        results = asyncio.run(_run_validation(auth_service, endpoint))

        # Handle no endpoints scenario
        if not results:
            formatter.warning("No endpoints to check")
            raise typer.Exit(ExitCode.SUCCESS)

        # Determine overall status
        all_passed = all(r.passed for r in results)
        failures = [r for r in results if not r.passed]

        # Output results
        if cli_ctx.json_output:
            formatter.json(
                {
                    "status": "pass" if all_passed else "fail",
                    "results": [
                        {
                            "endpoint": r.endpoint_name,
                            "status": "PASS" if r.passed else "FAIL",
                            "error": r.error_message,
                            "details": r.error_details,
                        }
                        for r in results
                    ],
                }
            )
        else:
            # Display results table
            formatter.info("API Contract Validation Results")
            formatter.info("-" * 40)

            for result in results:
                status = "[green]PASS[/green]" if result.passed else "[red]FAIL[/red]"
                formatter.info(f"  {result.endpoint_name}: {status}")

                if not result.passed and result.error_message:
                    formatter.info(f"    Error: {result.error_message}")

                    if verbose and result.error_details:
                        for detail in result.error_details:
                            formatter.info(f"    {detail}")

            formatter.info("-" * 40)

            if all_passed:
                formatter.success(
                    f"All {len(results)} endpoint(s) validated successfully"
                )
            else:
                formatter.error(
                    f"{len(failures)} of {len(results)} endpoint(s) failed validation"
                )

        # Exit with appropriate code
        if not all_passed:
            raise typer.Exit(ExitCode.CONTRACT_ERROR)

    except TeamsPhoneError as e:
        formatter.error(e.message, remediation=e.remediation)
        raise typer.Exit(e.exit_code)
