"""Centers, zones, members and versioned configurations."""

from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException
from psycopg import Connection
from pydantic import BaseModel, Field

from ..auth import User, current_user, require_admin, require_center
from ..calibration import replay_config
from ..configs import active_config, config_by_id, seed_default
from ..db import Jsonb, fetchall, fetchone, get_conn

router = APIRouter(tags=["centers"])


class CenterIn(BaseModel):
    slug: str = Field(pattern="^[a-z0-9-]{2,40}$")
    name: str = Field(min_length=2, max_length=120)
    afp_center_id: str | None = None
    timezone: str = "America/Los_Angeles"
    locale: str = "en"
    danger_scale: str = Field(default="NAC", pattern="^(NAC|SAC)$")


class ZoneIn(BaseModel):
    name: str = Field(min_length=1, max_length=120)
    afp_zone_id: str | None = None
    band_labels: dict[str, str] | None = None


class ConfigDraftIn(BaseModel):
    parent_id: int | None = None
    # Only these parts of a config may be changed through the calibration workflow.
    danger_table: dict[str, list[int]] | None = None
    gradient_table: dict[str, list[list[int]]] | None = None
    likelihood_matrix: dict[str, dict[str, int]] | None = None
    show_danger_suggestion: bool | None = None
    band_labels: dict[str, str] | None = None
    rationale: str = Field(min_length=10)


class ApproveIn(BaseModel):
    rationale: str | None = None


@router.get("/centers")
def list_centers(conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    if user.is_admin:
        return fetchall(conn, "SELECT * FROM centers ORDER BY name")
    return fetchall(
        conn,
        "SELECT c.*, m.role FROM centers c JOIN memberships m ON m.center_id = c.id WHERE m.user_id = %s ORDER BY c.name",
        (user.id,),
    )


@router.post("/centers")
def create_center(body: CenterIn, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    require_admin(user)
    row = fetchone(
        conn,
        """INSERT INTO centers (slug, name, afp_center_id, timezone, locale, danger_scale)
           VALUES (%(slug)s, %(name)s, %(afp_center_id)s, %(timezone)s, %(locale)s, %(danger_scale)s) RETURNING *""",
        body.model_dump(),
    )
    seed_default(conn, row["id"], user.id)
    return row


@router.get("/centers/{center_id}")
def get_center(center_id: int, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    require_center(user, center_id)
    center = fetchone(conn, "SELECT * FROM centers WHERE id = %s", (center_id,))
    if not center:
        raise HTTPException(404, "Center not found")
    zones = fetchall(conn, "SELECT * FROM zones WHERE center_id = %s ORDER BY name", (center_id,))
    return {**center, "zones": zones, "role": user.role_in(center_id)}


@router.post("/centers/{center_id}/zones")
def create_zone(center_id: int, body: ZoneIn, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    require_center(user, center_id, admin=True)
    return fetchone(
        conn,
        "INSERT INTO zones (center_id, name, afp_zone_id, band_labels) VALUES (%s, %s, %s, %s) RETURNING *",
        (center_id, body.name, body.afp_zone_id, Jsonb(body.band_labels) if body.band_labels else None),
    )


@router.get("/centers/{center_id}/members")
def list_members(center_id: int, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    require_center(user, center_id, admin=True)
    return fetchall(
        conn,
        """SELECT u.id, u.email, u.name, u.last_login_at, m.role FROM memberships m JOIN users u ON u.id = m.user_id
           WHERE m.center_id = %s ORDER BY u.name""",
        (center_id,),
    )


@router.get("/centers/{center_id}/config")
def get_active_config(center_id: int, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    require_center(user, center_id)
    return active_config(conn, center_id)


@router.get("/centers/{center_id}/configs")
def list_configs(center_id: int, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    require_center(user, center_id)
    return fetchall(
        conn,
        """SELECT cc.id, cc.version, cc.status, cc.rationale, cc.parent_id, cc.created_at, cc.proposed_at, cc.approved_at,
                  cu.name AS created_by_name, au.name AS approved_by_name
           FROM center_configs cc
           LEFT JOIN users cu ON cu.id = cc.created_by LEFT JOIN users au ON au.id = cc.approved_by
           WHERE cc.center_id = %s ORDER BY cc.version DESC""",
        (center_id,),
    )


@router.get("/configs/{config_id}")
def get_config(config_id: int, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    row = config_by_id(conn, config_id)
    require_center(user, row["center_id"])
    return row


def _validate_tables(cfg: dict) -> None:
    table = cfg["dangerTable"]
    cols = len(cfg["sizes"]) and len(table.get("1", []))
    for lk in ("1", "2", "3", "4", "5"):
        row = table.get(lk)
        if not isinstance(row, list) or len(row) != cols or any(not isinstance(v, int) or not 0 <= v <= 5 for v in row):
            raise HTTPException(422, f"Danger table row {lk} must have {cols} levels between 0 and 5.")
    for dist, row in cfg["likelihoodMatrix"].items():
        if any(not isinstance(v, int) or not 1 <= v <= 5 for v in row.values()):
            raise HTTPException(422, f"Likelihood matrix row {dist} must contain levels 1-5.")


@router.post("/centers/{center_id}/configs")
def create_draft(center_id: int, body: ConfigDraftIn, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    """Create a new DRAFT version from a parent (default: the active one). Never changes the active config."""
    require_center(user, center_id, admin=True)
    parent = config_by_id(conn, body.parent_id) if body.parent_id else active_config(conn, center_id)
    if body.parent_id and parent["center_id"] != center_id:
        raise HTTPException(422, "Parent belongs to another center")
    cfg = dict(parent["config"])
    if body.danger_table is not None:
        cfg["dangerTable"] = body.danger_table
    if body.gradient_table is not None:
        cfg["gradientTable"] = body.gradient_table
    if body.likelihood_matrix is not None:
        cfg["likelihoodMatrix"] = body.likelihood_matrix
    if body.show_danger_suggestion is not None:
        cfg["showDangerSuggestion"] = body.show_danger_suggestion
    if body.band_labels is not None:
        cfg["bandLabels"] = body.band_labels
    _validate_tables(cfg)
    version = fetchone(conn, "SELECT coalesce(max(version), 0) + 1 AS v FROM center_configs WHERE center_id = %s", (center_id,))["v"]
    return fetchone(
        conn,
        """INSERT INTO center_configs (center_id, version, status, config, rationale, parent_id, created_by)
           VALUES (%s, %s, 'draft', %s, %s, %s, %s) RETURNING id, version, status""",
        (center_id, version, Jsonb(cfg), body.rationale, parent["id"], user.id),
    )


@router.post("/configs/{config_id}/propose")
def propose(config_id: int, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    row = config_by_id(conn, config_id)
    require_center(user, row["center_id"], admin=True)
    if row["status"] != "draft":
        raise HTTPException(409, "Only drafts can be proposed")
    conn.execute("UPDATE center_configs SET status = 'proposed', proposed_at = now() WHERE id = %s", (config_id,))
    return {"ok": True}


@router.post("/configs/{config_id}/approve")
def approve(config_id: int, body: ApproveIn, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    """Activate a proposed version. Needs a DIFFERENT center admin (or a site admin) than its author."""
    row = config_by_id(conn, config_id)
    require_center(user, row["center_id"], admin=True)
    if row["status"] != "proposed":
        raise HTTPException(409, "Only proposed versions can be approved")
    if row["created_by"] == user.id and not user.is_admin:
        raise HTTPException(403, "A different center admin must approve this version")
    conn.execute("UPDATE center_configs SET status = 'retired' WHERE center_id = %s AND status = 'active'", (row["center_id"],))
    conn.execute(
        """UPDATE center_configs SET status = 'active', approved_by = %s, approved_at = now(),
               rationale = coalesce(rationale, '') || coalesce(%s, '') WHERE id = %s""",
        (user.id, f"\n\nApproval note: {body.rationale}" if body.rationale else None, config_id),
    )
    return {"ok": True}


@router.get("/configs/{config_id}/replay")
def replay(config_id: int, holdout_season: str | None = None, conn: Connection = Depends(get_conn), user: User = Depends(current_user)):
    """Backtest a config against imported forecasts: agreement vs the active config and which suggestions change."""
    row = config_by_id(conn, config_id)
    require_center(user, row["center_id"])
    active = active_config(conn, row["center_id"])
    return replay_config(conn, row["center_id"], row["config"], active["config"], holdout_season)
