"""Cockpit routes for the Pulse scheduler — admin-gated.

The integration is intentionally single-call: `register(app, TEMPLATES)` adds
the routes AND wires the FastAPI startup hook that launches the background
scheduler loop. Caller in app.py just imports + invokes register().
"""

from __future__ import annotations

import asyncio
from datetime import datetime, timezone
from typing import Optional

from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates

from psyc import log
from psyc.lines import pulse


_log = log.get(__name__)

TICK_INTERVAL_SECONDS = 30


def _admin_ok(request: Request) -> bool:
    """Mirror of the local helper in app.py — admin session is just session['admin_ok']."""
    return bool(request.session.get("admin_ok"))


def _relative(dt: Optional[datetime]) -> str:
    """Human-friendly "3m ago" / "in 12m" / "now". None → '—'."""
    if dt is None:
        return "—"
    now = datetime.now(timezone.utc)
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=timezone.utc)
    delta = (dt - now).total_seconds()
    past = delta < 0
    secs = abs(int(delta))
    if secs < 5:
        return "now"
    if secs < 60:
        unit = f"{secs}s"
    elif secs < 3600:
        unit = f"{secs // 60}m"
    elif secs < 86400:
        unit = f"{secs // 3600}h"
    else:
        unit = f"{secs // 86400}d"
    return f"{unit} ago" if past else f"in {unit}"


def register(app: FastAPI, templates: Jinja2Templates) -> None:
    """Attach the /admin/pulse routes and the background scheduler loop to `app`."""

    @app.get("/admin/pulse", response_class=HTMLResponse)
    def pulse_view(request: Request) -> HTMLResponse:
        if not _admin_ok(request):
            return RedirectResponse("/admin", status_code=303)
        flash = request.query_params.get("flash", "")
        pipelines = pulse.state()
        return templates.TemplateResponse(
            request,
            "admin_pulse.html",
            {
                "pipelines": pipelines,
                "kill_switch": pulse.kill_switch_state(),
                "tick_interval": TICK_INTERVAL_SECONDS,
                "relative": _relative,
                "flash": flash,
            },
        )

    @app.post("/admin/pulse/kill")
    def pulse_toggle_kill(request: Request) -> RedirectResponse:
        if not _admin_ok(request):
            return RedirectResponse("/admin", status_code=303)
        new = not pulse.kill_switch_state()
        pulse.set_kill_switch(new)
        flash = "kill switch ARMED — all pipelines halted" if new else "kill switch disarmed — pulse resumes"
        return RedirectResponse(f"/admin/pulse?flash={flash}", status_code=303)

    @app.post("/admin/pulse/{name}/update")
    def pulse_update(
        request: Request,
        name: str,
        mode: str = Form(...),
        cadence_seconds: int = Form(...),
        enabled: Optional[str] = Form(None),
    ) -> RedirectResponse:
        if not _admin_ok(request):
            return RedirectResponse("/admin", status_code=303)
        try:
            pulse.set_mode(name, pulse.PulseMode(mode))
            pulse.set_cadence(name, int(cadence_seconds))
            pulse.set_enabled(name, enabled is not None)
            flash = f"updated {name}: mode={mode}, cadence={cadence_seconds}s, enabled={enabled is not None}"
        except (ValueError, KeyError) as exc:
            _log.warning("pulse.update.error", name=name, error=str(exc))
            flash = f"update failed: {exc}"
        return RedirectResponse(f"/admin/pulse?flash={flash}", status_code=303)

    @app.post("/admin/pulse/{name}/run")
    def pulse_run_now(request: Request, name: str) -> RedirectResponse:
        if not _admin_ok(request):
            return RedirectResponse("/admin", status_code=303)
        try:
            outcome, result = pulse.run_now(name)
            flash = f"{name} → {outcome}: {result[:120]}"
        except ValueError as exc:
            flash = f"run failed: {exc}"
        return RedirectResponse(f"/admin/pulse?flash={flash}", status_code=303)

    @app.on_event("startup")
    async def _start_pulse_loop() -> None:
        # Fire-and-forget; the loop catches its own exceptions and self-restarts.
        asyncio.create_task(pulse.start_background_loop(interval_seconds=TICK_INTERVAL_SECONDS))
        _log.info("pulse.routes.registered", tick=TICK_INTERVAL_SECONDS)
