"""FastAPI composition root for the cockpit. Read-only, server-rendered (Jinja2 + HTMX). create_app(repo)
takes the repo so tests can inject a seeded in-memory DB; create_app() builds it from settings."""
from __future__ import annotations

import datetime as dt
import logging
from pathlib import Path

from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

from fxhnt.adapters.persistence.forward_nav import ForwardNavRepo
from fxhnt.adapters.web.charts import nav_sparkline
from fxhnt.application.dashboard_service import DashboardService
from fxhnt.ports.dashboard import DashboardReadModel

_HERE = Path(__file__).parent
_log = logging.getLogger(__name__)


def create_app(repo: ForwardNavRepo, svc: DashboardReadModel | None = None, factory_store=None) -> FastAPI:
    app = FastAPI(title="fxhnt cockpit")
    app.mount("/static", StaticFiles(directory=_HERE / "static"), name="static")
    templates = Jinja2Templates(directory=str(_HERE / "templates"))
    svc = svc or DashboardService(repo)

    def _now() -> str:
        return dt.datetime.now(dt.UTC).strftime("%Y-%m-%d %H:%M UTC")

    @app.get("/healthz")
    def healthz() -> dict:
        repo.all_summaries()                       # touches the DB; fails loudly if unreachable
        return {"status": "ok"}

    @app.get("/", response_class=HTMLResponse)
    def cockpit(request: Request) -> HTMLResponse:
        histories = repo.all_nav_histories()

        def spark(sid: str) -> str:
            return nav_sparkline([p.nav for p in histories.get(sid, [])])
        return templates.TemplateResponse(request, "cockpit.html",
                                          {"fleet": svc.fleet(), "spark": spark, "now": _now()})

    @app.get("/strategy/{strategy_id}", response_class=HTMLResponse)
    def strategy(request: Request, strategy_id: str) -> HTMLResponse:
        d = svc.detail(strategy_id)
        if d is None:
            raise HTTPException(status_code=404, detail="unknown strategy")
        curve = nav_sparkline([p.nav for p in d.history], width=600, height=160)
        return templates.TemplateResponse(request, "strategy.html", {"d": d, "curve": curve, "now": _now()})

    @app.get("/factory", response_class=HTMLResponse)
    def factory(request: Request) -> HTMLResponse:
        strategies, total_trials = _factory_data()
        return templates.TemplateResponse(request, "factory.html",
                                          {"strategies": strategies, "total_trials": total_trials, "now": _now()})

    @app.get("/funnel", response_class=HTMLResponse)
    def funnel(request: Request) -> HTMLResponse:
        c = factory_store.latest_cycle() if factory_store is not None else None
        alloc = factory_store.allocation(c.cycle_id) if c is not None else []
        return templates.TemplateResponse(request, "factory_funnel.html", {"c": c, "alloc": alloc, "now": _now()})

    def _factory_data() -> tuple[list, int]:
        # The factory population lives in the existing operational store; read it best-effort so the cockpit
        # works even before the factory has run in this DB.
        try:
            from fxhnt.adapters.persistence.operational import SqlOperationalRepository
            from fxhnt.adapters.persistence.strategy_store import SqlStrategyStore
            from fxhnt.config import get_settings
            dsn = get_settings().operational_dsn
            runs = SqlOperationalRepository(dsn).list_runs(passed_only=True)
            trials = SqlStrategyStore(dsn).total_trials()
            return runs, trials
        except Exception as e:
            _log.debug("factory data unavailable: %s", e)
            return [], 0

    return app


def create_app_from_settings() -> FastAPI:
    from fxhnt.adapters.persistence.factory_store import FactoryStore
    from fxhnt.config import get_settings
    settings = get_settings()
    repo = ForwardNavRepo(settings.operational_dsn)
    repo.migrate()
    fs = FactoryStore(settings.operational_dsn)
    return create_app(repo, factory_store=fs)
