"""FastAPI application factory for the Radix Edge Agent."""

import asyncio
import logging
from contextlib import asynccontextmanager

from fastapi import Depends, FastAPI

from radix_edge.config import get_config
from radix_edge.db import init_db, close_db
from radix_edge.api.routes_health import router as health_router
from radix_edge.api.routes_gpus import router as gpus_router
from radix_edge.api.routes_jobs import router as jobs_router
from radix_edge.api.routes_admin import router as admin_router

logger = logging.getLogger("radix_edge")


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application lifespan — startup and shutdown."""
    config = get_config()
    logging.basicConfig(
        level=getattr(logging, config.log_level.upper(), logging.INFO),
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    )

    logger.info("Radix Edge Agent starting")
    logger.info("Database: %s", config.db_path)

    # Initialize database
    init_db()
    logger.info("Database initialized (12 tables)")

    # Reconcile state after restart
    from radix_edge.recovery import reconcile_on_startup
    recovery_summary = reconcile_on_startup()
    logger.info("Recovery: %s", recovery_summary)

    # Start GPU observer background task
    from radix_edge.observer.gpu_observer import observer_loop
    observer_task = asyncio.create_task(observer_loop())
    logger.info("GPU observer started")

    # Start scheduler background task
    from radix_edge.scheduler.scheduler import scheduling_loop
    scheduler_task = asyncio.create_task(scheduling_loop())
    logger.info("Scheduler started")

    # Start FEP controller and brain auto-tuner
    from radix_edge.meta.fep_controller import fep_loop
    from radix_edge.meta.brain_autotuner import autotuner_loop
    fep_task = asyncio.create_task(fep_loop())
    autotuner_task = asyncio.create_task(autotuner_loop())
    logger.info("FEP controller and brain auto-tuner started")

    # Start upstream control plane communication (if configured)
    upstream_tasks = []
    upstream_client = None
    if config.upstream_enabled and config.api_base and config.cluster_id:
        from radix_edge.upstream.token_manager import resolve_token
        from radix_edge.upstream.client import UpstreamClient
        from radix_edge.upstream.heartbeat import heartbeat_loop
        from radix_edge.upstream.job_sync import job_sync_loop

        cluster_token = await resolve_token(
            api_base=config.api_base,
            cluster_id=config.cluster_id,
            tenant_id=config.tenant_id,
            cluster_token=config.cluster_token,
            one_time_token=config.one_time_token,
            token_file=config.token_file,
        )

        upstream_client = UpstreamClient(
            api_base=config.api_base,
            cluster_id=config.cluster_id,
            tenant_id=config.tenant_id,
            cluster_token=cluster_token,
        )

        upstream_tasks.append(asyncio.create_task(heartbeat_loop(upstream_client)))
        upstream_tasks.append(asyncio.create_task(job_sync_loop(upstream_client)))
        logger.info("Upstream control plane connected (cluster=%s)", config.cluster_id)
    else:
        logger.info("Upstream control plane disabled")

    # Start mesh networking (if configured)
    mesh_tasks = []
    mesh_peer_clients: dict = {}
    mesh_discovery_inst = None
    if config.mesh_enabled and config.mesh_key:
        import uuid
        from radix_edge.mesh.peer_registry import PeerRegistry
        from radix_edge.mesh.coordinator import MeshCoordinator
        from radix_edge.mesh.discovery import MeshDiscovery, ManualPeerList, ZEROCONF_AVAILABLE
        from radix_edge.mesh.sync import (
            mesh_heartbeat_loop,
            mesh_discovery_loop,
            election_monitor_loop,
            coordinator_loop,
        )

        # Generate or load node_id
        node_id = config.mesh_node_id
        if not node_id:
            with get_db() as db_conn:
                row = db_conn.execute("SELECT value FROM config WHERE key = 'mesh_node_id'").fetchone()
                if row:
                    node_id = row["value"]
                else:
                    node_id = f"node-{uuid.uuid4().hex[:12]}"
                    db_conn.execute(
                        "INSERT INTO config (key, value) VALUES ('mesh_node_id', ?)",
                        (node_id,),
                    )

        # Create mesh objects
        mesh_registry = PeerRegistry(stale_threshold=config.mesh_stale_threshold)
        mesh_coordinator = MeshCoordinator(node_id, mesh_registry)

        # Discovery: prefer mDNS, fall back to manual
        if ZEROCONF_AVAILABLE and not config.mesh_peers:
            mesh_discovery_inst = MeshDiscovery(
                node_id=node_id,
                port=config.port,
                mesh_key=config.mesh_key,
            )
            await mesh_discovery_inst.start()
        else:
            mesh_discovery_inst = ManualPeerList(config.mesh_peers)

        # Store in app.state for route access
        app.state.mesh_registry = mesh_registry
        app.state.mesh_coordinator = mesh_coordinator
        app.state.mesh_key = config.mesh_key
        app.state.mesh_node_id = node_id

        # Include mesh router
        from radix_edge.mesh.routes_mesh import router as mesh_router
        app.include_router(mesh_router)

        # Start background tasks
        mesh_tasks.append(asyncio.create_task(
            mesh_heartbeat_loop(config, mesh_registry, mesh_peer_clients, node_id, mesh_coordinator)
        ))
        mesh_tasks.append(asyncio.create_task(
            mesh_discovery_loop(config, mesh_discovery_inst, mesh_registry, mesh_peer_clients, config.mesh_key)
        ))
        mesh_tasks.append(asyncio.create_task(
            election_monitor_loop(config, mesh_coordinator, mesh_registry, mesh_peer_clients)
        ))
        mesh_tasks.append(asyncio.create_task(
            coordinator_loop(config, mesh_coordinator, mesh_peer_clients)
        ))

        # Run initial election
        mesh_coordinator.elect()
        logger.info("Mesh networking started (node=%s, peers=%d)", node_id, mesh_registry.peer_count)
    else:
        logger.info("Mesh networking disabled")

    logger.info("Radix Edge Agent ready on %s:%d", config.host, config.port)

    yield

    # Shutdown
    logger.info("Radix Edge Agent shutting down")
    for task in (observer_task, scheduler_task, fep_task, autotuner_task, *upstream_tasks, *mesh_tasks):
        task.cancel()
        try:
            await task
        except asyncio.CancelledError:
            pass
    if upstream_client:
        await upstream_client.close()
    # Close mesh resources
    for pc in mesh_peer_clients.values():
        await pc.close()
    if mesh_discovery_inst and hasattr(mesh_discovery_inst, "stop"):
        await mesh_discovery_inst.stop()
    close_db()
    logger.info("Radix Edge Agent stopped")


def create_app() -> FastAPI:
    """Create and configure the FastAPI application."""
    from radix_edge.api.auth import require_auth

    app = FastAPI(
        title="Radix Edge Agent",
        description="GPU orchestration with information-theoretic scheduling",
        version="0.1.0",
        lifespan=lifespan,
    )

    # Health endpoints — no auth required
    app.include_router(health_router)

    # All other endpoints — require Bearer token
    app.include_router(gpus_router, dependencies=[Depends(require_auth)])
    app.include_router(jobs_router, dependencies=[Depends(require_auth)])
    app.include_router(admin_router, dependencies=[Depends(require_auth)])

    return app
