"""SubmitStream — FastAPI entry point.

Slim bootstrap: init schema, seed the admin account + a demo form, start the
notification retry worker, mount routers. Per-feature logic lives in app/routes/*.
"""

from __future__ import annotations

import asyncio
import os
import secrets

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles

import auth
import deps
import notify
from db import execute, init_db, query, query_one
from routes import admin, api, auth_routes, dashboard, public

app = FastAPI(title="SubmitStream", docs_url=None, redoc_url=None)

if deps.STATIC_DIR.exists():
    app.mount("/static", StaticFiles(directory=deps.STATIC_DIR), name="static")


@app.middleware("http")
async def csrf_cookie_mw(request: Request, call_next):
    """Ensure a double-submit CSRF token exists; expose it as request.state.csrf."""
    token = request.cookies.get(auth.CSRF_COOKIE)
    set_needed = not token
    if set_needed:
        token = auth.new_csrf_token()
    request.state.csrf = token
    response = await call_next(request)
    if set_needed:
        response.set_cookie(auth.CSRF_COOKIE, token, max_age=auth.SESSION_TTL_DAYS * 86400,
                            httponly=True, secure=True, samesite="lax")
    return response


@app.middleware("http")
async def readonly_viewas_mw(request: Request, call_next):
    """Enforce read-only while 'viewing as' another user: block any write (non-GET)
    except the view-as controls, logout, the public submit endpoints, and the
    token-authed management API."""
    if request.method not in ("GET", "HEAD", "OPTIONS"):
        p = request.url.path
        exempt = (p.startswith("/view-as/") or p == "/logout"
                  or p.startswith("/f/") or p.startswith("/api/"))
        if not exempt and deps.impersonation_active(request):
            return JSONResponse(
                {"ok": False, "error": "read-only while viewing as another user"},
                status_code=403)
    return await call_next(request)


app.include_router(public.router)
app.include_router(auth_routes.router)
app.include_router(dashboard.router)
app.include_router(admin.router)
app.include_router(api.router)

_worker_task: asyncio.Task | None = None


@app.on_event("startup")
async def startup() -> None:
    init_db()
    _seed_admin()
    _seed_demo_form()
    global _worker_task
    _worker_task = asyncio.create_task(_retry_worker())


@app.on_event("shutdown")
async def shutdown() -> None:
    if _worker_task:
        _worker_task.cancel()


def _seed_admin() -> None:
    """Create rian's admin row once and print a one-time setup link to the log."""
    if query_one("SELECT 1 FROM clients WHERE role = 'admin'"):
        return
    admin_id = execute(
        "INSERT INTO clients (name, email, role) VALUES (?, ?, 'admin')",
        ("Admin", "admin@local"),
    )
    token = auth.issue_token(admin_id, purpose="setup")
    print(
        "\n================ SUBMITSTREAM ADMIN SETUP ================\n"
        f"Open: {deps.APP_BASE_URL}/setup?token={token}\n"
        "Claim the admin account — set your real email + password there.\n"
        "=========================================================\n",
        flush=True,
    )


def _seed_demo_form() -> None:
    """A throwaway form so the pipeline is testable immediately (public_key 'demo')."""
    if query_one("SELECT 1 FROM forms LIMIT 1"):
        return
    seed_recipient = os.environ.get("SEED_NOTIFY", "rian@rian.ca")
    execute(
        "INSERT INTO forms (public_key, name, notify_recipients, redirect_url) "
        "VALUES (?, ?, ?, ?)",
        ("demo", "Demo Form", seed_recipient, f"{deps.APP_BASE_URL}/thanks"),
    )
    print(f"[seed] demo form ready at {deps.APP_BASE_URL}/f/demo "
          f"(notifies {seed_recipient})", flush=True)


async def _retry_worker() -> None:
    """Drain failed notifications whose backoff has elapsed. Sync work off the loop."""
    await asyncio.sleep(5)
    while True:
        try:
            due = query(
                "SELECT id FROM notifications WHERE status='failed' "
                "AND next_attempt_at IS NOT NULL AND next_attempt_at <= CURRENT_TIMESTAMP "
                "AND attempts < max_attempts ORDER BY next_attempt_at LIMIT 25"
            )
            for row in due:
                await asyncio.to_thread(notify.process_notification, row["id"])
        except Exception as exc:  # noqa: BLE001 — worker must never die
            print(f"[retry_worker] error: {exc}", flush=True)
        await asyncio.sleep(30)


@app.get("/", response_class=HTMLResponse)
def home(request: Request):
    user = deps.current_user(request)
    if user:
        from fastapi.responses import RedirectResponse
        return RedirectResponse("/dashboard", status_code=303)
    return deps.render("landing.html")


@app.get("/thanks", response_class=HTMLResponse)
def thanks():
    return deps.render("thanks.html")
