"""ESPresso Frame server: pulls photos from Immich, pre-processes them
for the panel, and serves ESP32 frames ready-to-display images.

This module is assembly only -- routes live in app/routers/:
  device.py       the firmware-facing /frame/* protocol (paths frozen)
  api_frames.py   the web UI's JSON API, /api/frames/{id}/...
  frame_pages.py  the per-frame Photos/Configuration/Stats pages
  pages.py        setup/login/claim/settings/admin
  manage.py       the limited manage-QR surface (/m/, /api/m/)
Storage is SQLite via models.py/db.py; migration.py imports a
pre-database config.json deployment on first boot."""

from __future__ import annotations

import logging

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from sqlalchemy import select

from . import migration
from .auth import (
    browser_token_valid,
    current_user,
    management_token,
    user_frames,
    users_exist,
)
from .db import SessionLocal
from .models import Frame
from .routers import api_frames, device, frame_pages, manage, pages
from .routers.common import shell_context

logger = logging.getLogger(__name__)

# Schema + legacy-config import, before the first request is served.
migration.run_migrations()

app = FastAPI(title="ESPresso Frame Server")
templates = Jinja2Templates(directory="app/templates")

app.mount("/static", StaticFiles(directory="app/static"), name="static")

app.include_router(device.router)
app.include_router(api_frames.router)
app.include_router(frame_pages.router)
app.include_router(pages.router)
app.include_router(manage.router)


@app.get("/health")
def health() -> dict:
    return {"status": "ok"}


def _device_credential_redirect(request: Request, db, allow_legacy: bool) -> str | None:
    """The on-frame manage QR points at the server root with the device's
    own credentials (new firmware: ?id=&token=; deployed firmware:
    ?token=<legacy shared token>). Those scans get the frame's limited
    manage page -- never the full UI, which requires a login.
    allow_legacy is False before /setup has run: at that point a bare
    ?token= hit is the admin coming through the token prompt to do
    first-run setup, not a QR scan."""
    device_id = request.query_params.get("id", "").strip().lower()
    token = request.query_params.get("token", "")
    if device_id and token:
        frame = db.scalars(select(Frame).where(Frame.device_id == device_id)).first()
        if frame is not None and token == frame.device_token:
            return f"/m/{frame.manage_token}"
    if allow_legacy and token and management_token() and token == management_token():
        frame = db.scalars(
            select(Frame).where(Frame.legacy_token_enabled == True)  # noqa: E712
        ).first()
        if frame is not None:
            return f"/m/{frame.manage_token}"
    return None


@app.get("/", response_class=HTMLResponse)
def index(request: Request):
    """Routing hub: manage-QR scans go to the limited manage page, users
    land on their first frame (or an empty-state page), and everyone
    else is walked through setup/login."""
    with SessionLocal() as db:
        have_users = users_exist(db)
        manage_redirect = _device_credential_redirect(request, db, allow_legacy=have_users)
        if manage_redirect is not None:
            return RedirectResponse(manage_redirect, status_code=303)

        user = current_user(request, db)
        if user is None:
            if not have_users:
                if management_token() and not browser_token_valid(request):
                    supplied = request.query_params.get("token")
                    return templates.TemplateResponse(
                        "token_prompt.html", {"request": request, "wrong": supplied is not None}
                    )
                # Pre-setup: reachable (optionally token-gated), nudge setup.
                return RedirectResponse("/setup", status_code=303)
            return RedirectResponse("/login", status_code=303)

        frames = user_frames(db, user)
        if frames:
            return RedirectResponse(f"/frames/{frames[0].id}", status_code=303)
        return templates.TemplateResponse("frames_empty.html", shell_context(request, db, user))
