"""FastAPI surface for Azure discovery."""

from __future__ import annotations

import os
from pathlib import Path
from typing import Optional

from fastapi import Depends, FastAPI, HTTPException, Security
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.routing import APIRouter
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

from .adt_types import AzureDiscoveryRequest, AzureDiscoveryResponse
from .auth import AuthBackend, get_auth_provider, initialize_auth
from .auth.models import AuthenticationError, UserContext
from .config.api_config import APIConfig
from .middleware.audit import AuditLogMiddleware, FileAuditLogWriter, LoggerAuditLogWriter
from .middleware.quota import InMemoryQuotaStore, QuotaMiddleware
from .middleware.rate_limit import RateLimitConfig as MiddlewareRateLimitConfig
from .middleware.rate_limit import RateLimitMiddleware
from .orchestrator import run_discovery
from .utils.config_files import deep_merge, load_config_file
from .utils.logging import get_logger

LOGGER = get_logger()

# ---------------------------------------------------------------------------
# Load API configuration
# ---------------------------------------------------------------------------

_api_config_path = os.getenv("AZURE_DISCOVERY_API_CONFIG")
_api_config = APIConfig()
if _api_config_path:
    try:
        _api_config = APIConfig.from_toml(Path(_api_config_path))
        LOGGER.info(
            "Loaded API configuration",
            extra={"context": {"path": _api_config_path}},
        )
    except Exception as exc:  # noqa: BLE001
        LOGGER.error(
            "Failed to load AZURE_DISCOVERY_API_CONFIG; using defaults",
            extra={"context": {"path": _api_config_path, "error": str(exc)}},
        )

# ---------------------------------------------------------------------------
# Initialize authentication
# ---------------------------------------------------------------------------

_auth_backend = AuthBackend(_api_config.auth.backend)
_auth_init_config: dict = {}

if _auth_backend == AuthBackend.AZURE_AD and _api_config.auth.azure_ad:
    _auth_init_config = {
        "tenant_id": _api_config.auth.azure_ad.tenant_id,
        "client_id": _api_config.auth.azure_ad.client_id,
        "audience": _api_config.auth.azure_ad.audience,
    }
elif _auth_backend == AuthBackend.API_KEY and _api_config.auth.api_key:
    _auth_init_config = {"keys": _api_config.auth.api_key.keys}

initialize_auth(_auth_backend, _auth_init_config)

# ---------------------------------------------------------------------------
# Application setup
# ---------------------------------------------------------------------------

app = FastAPI(title="Azure Discovery", version="1.0.0")

# CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=os.getenv("AZURE_DISCOVERY_CORS_ORIGINS", "*").split(","),
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Audit logging middleware
if _api_config.audit_log.enabled:
    if _api_config.audit_log.log_file:
        _audit_writer = FileAuditLogWriter(_api_config.audit_log.log_file)
    else:
        _audit_writer = LoggerAuditLogWriter()
    app.add_middleware(
        AuditLogMiddleware,
        writer=_audit_writer,
        include_request_body=_api_config.audit_log.include_request_body,
        include_response_body=_api_config.audit_log.include_response_body,
    )

# Quota middleware
if _api_config.quota.enabled:
    _quota_store = InMemoryQuotaStore(
        default_limit=_api_config.quota.default_limit,
        period_days=_api_config.quota.period_days,
    )
    app.add_middleware(QuotaMiddleware, quota_store=_quota_store)

# Rate limiting middleware
if _api_config.rate_limit.enabled:
    _rate_limit_config = MiddlewareRateLimitConfig(
        enabled=_api_config.rate_limit.enabled,
        requests_per_minute=_api_config.rate_limit.requests_per_minute,
        burst_size=_api_config.rate_limit.burst_size,
        redis_url=_api_config.rate_limit.redis_url,
    )
    app.add_middleware(RateLimitMiddleware, config=_rate_limit_config)

# ---------------------------------------------------------------------------
# Authentication dependency
# ---------------------------------------------------------------------------

security = HTTPBearer(auto_error=False)


async def authenticate_request(
    credentials: Optional[HTTPAuthorizationCredentials] = Security(security),
) -> Optional[UserContext]:
    """Dependency for route authentication.

    Returns None when auth backend is 'none' (development mode).
    Raises 401 for all other backends when credentials are missing or invalid.
    """
    auth_provider = get_auth_provider()

    # No auth configured (development mode)
    if auth_provider is None:
        return None

    if not credentials:
        raise HTTPException(status_code=401, detail="Missing authentication credentials")

    try:
        user_context = await auth_provider.authenticate(credentials.credentials)
        return UserContext(**user_context)
    except AuthenticationError as exc:
        raise HTTPException(status_code=401, detail=str(exc))
    except Exception:
        raise HTTPException(status_code=401, detail="Invalid credentials")


# ---------------------------------------------------------------------------
# Request defaults from config file
# ---------------------------------------------------------------------------

_DEFAULT_REQUEST_OVERRIDES: dict = {}
_default_config_path = os.getenv("AZURE_DISCOVERY_CONFIG")
if _default_config_path:
    try:
        _DEFAULT_REQUEST_OVERRIDES = load_config_file(Path(_default_config_path))
        LOGGER.info(
            "Loaded AzureDiscoveryRequest defaults from config",
            extra={"context": {"path": _default_config_path}},
        )
    except Exception as exc:  # noqa: BLE001
        LOGGER.error(
            "Failed to load AZURE_DISCOVERY_CONFIG; continuing without defaults",
            extra={"context": {"path": _default_config_path, "error": str(exc)}},
        )

# ---------------------------------------------------------------------------
# API v1 versioned router
# ---------------------------------------------------------------------------

v1_router = APIRouter(prefix="/v1", tags=["v1"])


@v1_router.post(
    "/discover",
    response_model=AzureDiscoveryResponse,
    tags=["discovery"],
)
async def v1_discover_endpoint(
    request: AzureDiscoveryRequest,
    user: Optional[UserContext] = Depends(authenticate_request),
) -> AzureDiscoveryResponse:
    """Versioned discovery endpoint (v1)."""
    return await _run_discovery_with_defaults(request)


@v1_router.get("/healthz", tags=["system"])
async def v1_health_check() -> dict:
    return {"status": "ok", "version": "v1"}


app.include_router(v1_router)

# ---------------------------------------------------------------------------
# Root (unversioned) endpoints — backward compatible
# ---------------------------------------------------------------------------


@app.get("/healthz", tags=["system"])
async def health_check() -> dict:
    return {"status": "ok"}


@app.post(
    "/discover",
    response_model=AzureDiscoveryResponse,
    tags=["discovery"],
)
async def discover_endpoint(
    request: AzureDiscoveryRequest,
    user: Optional[UserContext] = Depends(authenticate_request),
) -> AzureDiscoveryResponse:
    return await _run_discovery_with_defaults(request)


@app.get("/visuals/{file_name}", tags=["visualization"])
async def visualization_endpoint(file_name: str) -> FileResponse:
    if not file_name:
        raise HTTPException(status_code=400, detail="file_name missing")
    html_path = Path("artifacts/graphs") / file_name
    if not html_path.is_file():
        raise HTTPException(status_code=404, detail="Visualization not found")
    return FileResponse(html_path)


# ---------------------------------------------------------------------------
# Shared helpers
# ---------------------------------------------------------------------------


async def _run_discovery_with_defaults(
    request: AzureDiscoveryRequest,
) -> AzureDiscoveryResponse:
    """Apply default overrides and run discovery."""
    if _DEFAULT_REQUEST_OVERRIDES:
        incoming = request.model_dump(exclude_unset=True)
        merged = deep_merge(_DEFAULT_REQUEST_OVERRIDES, incoming)
        request = AzureDiscoveryRequest.model_validate(merged)

    LOGGER.info(
        "API discovery invoked",
        extra={"context": {"tenant_id": request.tenant_id}},
    )
    return await run_discovery(request)
