"""HTML-only ops endpoints — /api/markets-bar and /api/health.

These are HTMX partials consumed by the dashboard. They return HTML by
default (not JSON) and are not included in the OpenAPI schema.
"""
from __future__ import annotations

from fastapi import APIRouter, Depends, Query, Request
from fastapi.responses import HTMLResponse, JSONResponse
from sqlalchemy import desc, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.auth import require_token
from app.db import get_session, utcnow
from app.models import JobRun, Quote
from app.routers.api import _age_seconds, _fmt_age
from app.schemas import HealthOut, JobStatus
from app.templates_env import templates

router = APIRouter(dependencies=[Depends(require_token)])

JOB_NAMES = ("market_job", "news_job", "ai_log_job", "rollup_job",
             "indicator_summary_job", "universe_flush_job",
             "email_digest_job")
JOB_STALE_HOURS = 2.0  # job is "warn" if its last success was >2h ago

# Market → headline index mapping for the sticky bottom bar. Symbols must
# be present in config/default.toml so market_job populates `quotes`.
_MARKET_INDEX = {
    "NYSE":  ("^GSPC",     "S&P 500"),
    "LSE":   ("^FTSE",     "FTSE 100"),
    # XETRA → Euro Stoxx 50 rather than ^GDAXI: Yahoo's DAX ticker is
    # patchy via the chart endpoint, and ^STOXX50E is already tracked in
    # config/default.toml's equity group.
    "XETRA": ("^STOXX50E", "STOXX 50"),
    "JPX":   ("^N225",     "Nikkei 225"),
    "HKEX":  ("^HSI",      "Hang Seng"),
    "SSE":   ("000300.SS", "CSI 300"),
}


def _fmt_price(p: float | None) -> str:
    if p is None:
        return "—"
    if abs(p) >= 1000:
        return f"{p:,.0f}"
    if abs(p) >= 100:
        return f"{p:,.1f}"
    return f"{p:,.2f}"


@router.get("/markets-bar", response_class=HTMLResponse, include_in_schema=False)
async def markets_bar(
    request: Request,
    session: AsyncSession = Depends(get_session),
    as_: str | None = Query(default=None, alias="as"),
):
    """The sticky bottom-bar payload: per-market open/close status with the
    market's headline index price + 1d change. Refreshed by HTMX every 60s.
    """
    from app.services.markets import all_statuses

    statuses = all_statuses()
    # Latest quote per headline-index symbol in one query.
    wanted_syms = [sym for sym, _ in _MARKET_INDEX.values()]
    sub = (
        select(Quote.symbol, func.max(Quote.fetched_at).label("mx"))
        .where(Quote.symbol.in_(wanted_syms))
        .group_by(Quote.symbol)
        .subquery()
    )
    rows = (await session.execute(
        select(Quote).join(
            sub,
            (Quote.symbol == sub.c.symbol) & (Quote.fetched_at == sub.c.mx),
        )
    )).scalars().all()
    by_sym = {q.symbol: q for q in rows}

    markets: list[dict] = []
    for st in statuses:
        sym, label = _MARKET_INDEX.get(st["code"], (None, None))
        q = by_sym.get(sym) if sym else None
        idx = None
        if q is not None and q.price is not None:
            idx = {
                "symbol": q.symbol,
                "label": label,
                "price_fmt": _fmt_price(q.price),
                "change_1d_pct": (q.changes or {}).get("1d"),
            }
        markets.append({
            "code": st["code"],
            "label": st["label"],
            "open": st["open"],
            "until_iso": st["until"].isoformat(),
            "until_hhmm": st["until"].strftime("%H:%M"),
            "index": idx,
        })

    return templates.TemplateResponse(
        request, "partials/markets_bar.html",
        {"markets": markets},
    )


@router.get("/health", response_class=HTMLResponse, include_in_schema=False)
async def health_html(
    request: Request,
    session: AsyncSession = Depends(get_session),
    as_: str | None = Query(default=None, alias="as"),
):
    """Returns an HTML fragment by default (the ops footer); ?as=json returns the
    structured object. The default is HTML because that's how the dashboard
    consumes it; CLI/curl users will pass ?as=json."""
    try:
        await session.execute(select(func.now()))
        db_ok = True
    except Exception:
        db_ok = False

    now = utcnow()
    jobs: list[dict] = []
    structured: list[JobStatus] = []
    for name in JOB_NAMES:
        row = (await session.execute(
            select(JobRun).where(JobRun.name == name)
            .order_by(desc(JobRun.started_at)).limit(1)
        )).scalar_one_or_none()
        if row is None:
            jobs.append({"name": name, "led": "idle", "age": "—",
                         "last_finished": None})
            structured.append(JobStatus(name=name))
            continue
        if row.status == "success":
            secs = _age_seconds(now, row.finished_at or row.started_at) or 0
            led = "ok" if secs < JOB_STALE_HOURS * 3600 else "warn"
        elif row.status == "skipped":
            led = "warn"
        elif row.status == "running":
            led = "warn"
        else:
            led = "err"
        jobs.append({
            "name": name, "led": led,
            "age": _fmt_age(now, row.finished_at or row.started_at),
            "last_finished": row.finished_at,
        })
        structured.append(JobStatus(
            name=name, last_started=row.started_at,
            last_finished=row.finished_at, status=row.status,
            error=row.error, items_written=row.items_written,
        ))

    if as_ == "json":
        return JSONResponse(
            HealthOut(db="ok" if db_ok else "down", jobs=structured).model_dump(mode="json")
        )
    return templates.TemplateResponse(
        request, "partials/ops_footer.html",
        {"db_ok": db_ok, "jobs": jobs},
    )
