# src/veronica/api/app.py
"""FastAPI application factory for the Veronica control-plane API."""

from __future__ import annotations

import logging
import os
from contextlib import asynccontextmanager
from typing import AsyncGenerator

from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse

from veronica.api.auth import APIKeyMiddleware
from veronica.api.policy_registry import PolicyRegistry
from veronica.distribution.policy_distributor import PolicyDistributor
from veronica.ingest.event_ingestor import EventIngestor
from veronica.rollouts.registry import RolloutRegistry
from veronica.store import MemoryStore
from veronica.tenants import TenantRegistry

logger = logging.getLogger(__name__)


def _get_version() -> str:
    """Return veronica version via deferred import to avoid circular imports."""
    try:
        from veronica import __version__

        return __version__
    except Exception:
        return "unknown"


@asynccontextmanager
async def _lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
    """Initialize and teardown shared resources."""
    database_url = os.environ.get("VERONICA_DATABASE_URL", "")
    pg_store = None
    if database_url:
        try:
            from veronica.pg_store import PgStore

            pg_store = PgStore(dsn=database_url)
            store = pg_store
            logger.info("Veronica API using PostgreSQL store")
        except ImportError:
            logger.warning(
                "VERONICA_DATABASE_URL is set but psycopg/psycopg_pool is not installed. "
                "Falling back to MemoryStore. Install with: pip install veronica-cp[postgres]"
            )
            store = MemoryStore()  # type: ignore[assignment]
        except Exception:
            # Mask credentials before logging -- never log raw DSN
            safe_dsn = database_url.split("@")[-1] if "@" in database_url else database_url
            logger.exception(
                "Failed to connect to PostgreSQL (host: %s). Falling back to MemoryStore.",
                safe_dsn,
            )
            store = MemoryStore()
    else:
        store = MemoryStore()

    distributor = PolicyDistributor()
    ingestor = EventIngestor()
    registry = PolicyRegistry(distributor)
    tenant_registry = TenantRegistry()
    rollout_registry = RolloutRegistry()

    app.state.store = store
    app.state.distributor = distributor
    app.state.ingestor = ingestor
    app.state.registry = registry
    app.state.tenant_registry = tenant_registry
    app.state.rollout_registry = rollout_registry

    # Fail-closed startup validation: require VERONICA_API_KEY unless auth is disabled.
    auth_disabled = os.environ.get("VERONICA_AUTH_DISABLED", "") == "1"
    api_key_set = bool(os.environ.get("VERONICA_API_KEY", "").strip())
    if not auth_disabled and not api_key_set:
        logger.critical(
            "VERONICA_API_KEY is not set and VERONICA_AUTH_DISABLED is not 1. "
            "The API is running without authentication. "
            "Set VERONICA_API_KEY or VERONICA_AUTH_DISABLED=1 to suppress this warning."
        )
        app.state.startup_valid = False
    else:
        app.state.startup_valid = True

    logger.info("Veronica API started")
    yield
    if pg_store is not None:
        pg_store.close()
    logger.info("Veronica API shutting down")


def create_app() -> FastAPI:
    """Create and configure the FastAPI application."""
    from veronica.api.routes import events as events_router
    from veronica.api.routes import export as export_router
    from veronica.api.routes import health as health_router
    from veronica.api.routes import incidents as incidents_router
    from veronica.api.routes import policies as policies_router
    from veronica.api.routes import replay as replay_router
    from veronica.api.routes import runs as runs_router
    from veronica.api.routes import simulate as simulate_router
    from veronica.api.routes import tenants as tenants_router
    from veronica.rollouts import router as rollouts_router_module
    from veronica.ui.router import mount_static
    from veronica.ui.router import router as ui_router

    app = FastAPI(
        title="VERONICA Control Plane API",
        version=_get_version(),
        description=(
            "Execution OS API for LLM systems.\n\n"
            "Provides policy management, event audit, and side-effect-free simulation "
            "built on veronica-core."
        ),
        openapi_tags=[
            {"name": "health", "description": "Liveness and readiness checks"},
            {"name": "policies", "description": "Policy CRUD and versioned updates"},
            {"name": "simulate", "description": "Side-effect-free policy simulation"},
            {"name": "events", "description": "Paginated, filterable event audit log"},
            {
                "name": "export",
                "description": "Full JSON export for backup and migration",
            },
            {
                "name": "rollouts",
                "description": (
                    "Rollout pipeline management "
                    "(draft->simulated->approved->promoted->active->revoked)"
                ),
            },
        ],
        lifespan=_lifespan,
    )

    app.add_middleware(APIKeyMiddleware)

    _cors_env = os.environ.get("VERONICA_CORS_ORIGINS", "")
    if _cors_env and _cors_env != "*":
        # Explicit origin list with credentials enabled
        _cors_origins: list[str] = [
            o.strip() for o in _cors_env.split(",") if o.strip()
        ]
        _cors_credentials = True
    else:
        # Default or explicit wildcard -- credentials must be disabled (browser security requirement)
        _cors_origins = ["*"]
        _cors_credentials = False

    app.add_middleware(
        CORSMiddleware,
        allow_origins=_cors_origins,
        allow_credentials=_cors_credentials,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    @app.exception_handler(HTTPException)
    async def http_exception_handler(
        request: Request, exc: HTTPException
    ) -> JSONResponse:
        return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail})

    @app.exception_handler(Exception)
    async def generic_exception_handler(
        request: Request, exc: Exception
    ) -> JSONResponse:
        logger.exception("Unhandled exception: %s", exc)
        detail = (
            str(exc)
            if os.environ.get("VERONICA_DEBUG") == "1"
            else "Internal server error"
        )
        return JSONResponse(status_code=500, content={"detail": detail})

    app.include_router(health_router.router)
    app.include_router(events_router.router)
    app.include_router(runs_router.router)
    app.include_router(incidents_router.router)
    app.include_router(policies_router.router)
    app.include_router(simulate_router.router)
    app.include_router(replay_router.router)
    app.include_router(export_router.router)
    app.include_router(tenants_router.router)
    app.include_router(rollouts_router_module.router)
    app.include_router(ui_router)
    mount_static(app)

    return app
