"""
Admin: Referenzwert-Typen (Katalog für persönliche Referenzwerte).

Nur Admins; Nutzer sehen nur aktive Typen über /api/reference-value-types.
"""
import re
from typing import Any, Optional

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from psycopg2 import errors as pg_errors
from psycopg2.extras import Json

from auth import require_admin
from db import get_db, get_cursor, r2d
from reference_value_validation import VALUE_DATA_TYPES

router = APIRouter(prefix="/api/admin/reference-value-types", tags=["admin", "reference-value-types"])

KEY_PATTERN = re.compile(r"^[a-z][a-z0-9_]{0,62}$")


def _serialize_type(row: dict[str, Any]) -> dict[str, Any]:
    if not row:
        return row
    out = dict(row)
    ca = out.get("created_at")
    if ca is not None and hasattr(ca, "isoformat"):
        out["created_at"] = ca.isoformat()
    return out


def _normalize_value_data_type(v: str) -> str:
    s = (v or "decimal").strip().lower()
    if s not in VALUE_DATA_TYPES:
        raise HTTPException(
            400,
            f"Ungültiger Datentyp: {s}. Erlaubt: {', '.join(sorted(VALUE_DATA_TYPES))}",
        )
    return s


class ReferenceValueTypeAdminCreate(BaseModel):
    key: str = Field(..., min_length=1, max_length=64)
    label: str = Field(..., min_length=1, max_length=200)
    description: Optional[str] = None
    category: Optional[str] = None
    default_unit: Optional[str] = Field(None, max_length=32)
    value_data_type: str = "decimal"
    validation_rules: Optional[dict] = None
    active: bool = True
    metadata: Optional[dict] = None


class ReferenceValueTypeAdminUpdate(BaseModel):
    label: Optional[str] = Field(None, min_length=1, max_length=200)
    description: Optional[str] = None
    category: Optional[str] = None
    default_unit: Optional[str] = Field(None, max_length=32)
    value_data_type: Optional[str] = None
    validation_rules: Optional[dict] = None
    active: Optional[bool] = None
    metadata: Optional[dict] = None


class ReferenceValueTypesReorderBody(BaseModel):
    ordered_ids: list[int] = Field(..., min_length=1)


def _normalize_key(key: str) -> str:
    k = key.strip().lower()
    if not KEY_PATTERN.match(k):
        raise HTTPException(
            400,
            "Ungültiger Schlüssel: nur Kleinbuchstaben, Ziffern, Unterstriche; muss mit Buchstabe beginnen.",
        )
    return k


def _unit_or_none(u: Optional[str]) -> Optional[str]:
    if u is None:
        return None
    s = u.strip()
    return s if s else None


def _cat_or_none(c: Optional[str]) -> Optional[str]:
    if c is None:
        return None
    s = c.strip()
    return s if s else None


@router.get("")
def admin_list_reference_value_types(session: dict = Depends(require_admin)):
    """Alle Typen inkl. inaktiver (Admin-Übersicht)."""
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            """
            SELECT
                id, key, label, description, category, default_unit, value_data_type,
                validation_rules, sort_order, active, metadata, created_at
            FROM reference_value_types
            ORDER BY sort_order ASC, id ASC
            """
        )
        return [_serialize_type(r2d(r)) for r in cur.fetchall()]


@router.post("/reorder")
def admin_reorder_reference_value_types(
    body: ReferenceValueTypesReorderBody,
    session: dict = Depends(require_admin),
):
    """Globale Reihenfolge setzen (sort_order = 10, 20, …). Liste muss alle Typ-IDs genau einmal enthalten."""
    ids = body.ordered_ids
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("SELECT id FROM reference_value_types")
        all_ids = sorted([r["id"] for r in cur.fetchall()])
    if len(ids) != len(all_ids):
        raise HTTPException(
            400,
            f"Erwartet {len(all_ids)} Einträge, erhalten {len(ids)}.",
        )
    if sorted(ids) != all_ids:
        raise HTTPException(400, "Die ID-Liste muss alle Kennwert-Typen exakt einmal enthalten (keine Duplikate).")
    if len(set(ids)) != len(ids):
        raise HTTPException(400, "Doppelte IDs sind nicht erlaubt.")

    with get_db() as conn:
        cur = get_cursor(conn)
        for idx, tid in enumerate(ids):
            cur.execute(
                "UPDATE reference_value_types SET sort_order = %s WHERE id = %s",
                ((idx + 1) * 10, tid),
            )
    return {"ok": True}


@router.get("/{type_id}")
def admin_get_reference_value_type(type_id: int, session: dict = Depends(require_admin)):
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            """
            SELECT
                id, key, label, description, category, default_unit, value_data_type,
                validation_rules, sort_order, active, metadata, created_at
            FROM reference_value_types WHERE id = %s
            """,
            (type_id,),
        )
        row = cur.fetchone()
    if not row:
        raise HTTPException(404, "Typ nicht gefunden")
    return _serialize_type(r2d(row))


@router.post("")
def admin_create_reference_value_type(
    body: ReferenceValueTypeAdminCreate,
    session: dict = Depends(require_admin),
):
    key = _normalize_key(body.key)
    vdt = _normalize_value_data_type(body.value_data_type)
    if not _unit_or_none(body.default_unit):
        raise HTTPException(400, "Standard-Einheit ist erforderlich (wird bei der Erfassung fix verwendet).")
    meta = body.metadata if body.metadata is not None else {}
    rules = body.validation_rules if body.validation_rules is not None else {}
    if vdt == "enum":
        av = rules.get("allowed_values") if isinstance(rules, dict) else []
        if not isinstance(av, list) or not [x for x in av if str(x).strip()]:
            raise HTTPException(
                400,
                "Datentyp ENUM erfordert unter Plausibilisierung eine nicht-leere Liste „Erlaubte Werte“.",
            )
    du = _unit_or_none(body.default_unit)

    with get_db() as conn:
        cur = get_cursor(conn)
        try:
            cur.execute("SELECT COALESCE(MAX(sort_order), 0) AS m FROM reference_value_types")
            next_sort = int(cur.fetchone()["m"]) + 10
            cur.execute(
                """
                INSERT INTO reference_value_types
                    (key, label, description, category, default_unit, value_data_type,
                     validation_rules, sort_order, active, metadata)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                RETURNING
                    id, key, label, description, category, default_unit, value_data_type,
                    validation_rules, sort_order, active, metadata, created_at
                """,
                (
                    key,
                    body.label.strip(),
                    body.description.strip() if body.description else None,
                    _cat_or_none(body.category),
                    du,
                    vdt,
                    Json(rules),
                    next_sort,
                    body.active,
                    Json(meta),
                ),
            )
            return _serialize_type(r2d(cur.fetchone()))
        except pg_errors.UniqueViolation:
            raise HTTPException(409, "Ein Typ mit diesem Schlüssel existiert bereits.")


@router.put("/{type_id}")
def admin_update_reference_value_type(
    type_id: int,
    body: ReferenceValueTypeAdminUpdate,
    session: dict = Depends(require_admin),
):
    patch = body.model_dump(exclude_unset=True)
    if not patch:
        raise HTTPException(400, "Keine Felder zum Aktualisieren")

    if "value_data_type" in patch and patch["value_data_type"] is not None:
        patch["value_data_type"] = _normalize_value_data_type(patch["value_data_type"])
    if "default_unit" in patch:
        patch["default_unit"] = _unit_or_none(patch.get("default_unit"))
        if patch["default_unit"] is None:
            raise HTTPException(400, "Standard-Einheit darf nicht leer werden.")
    if "description" in patch and patch["description"] is not None:
        patch["description"] = patch["description"].strip() or None
    if "category" in patch:
        patch["category"] = _cat_or_none(patch.get("category"))
    if "metadata" in patch:
        patch["metadata"] = Json(patch["metadata"] if patch["metadata"] is not None else {})
    if "validation_rules" in patch:
        patch["validation_rules"] = Json(patch["validation_rules"] if patch["validation_rules"] is not None else {})

    cols = []
    vals = []
    for k, v in patch.items():
        cols.append(f"{k} = %s")
        vals.append(v)
    vals.append(type_id)

    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            f"""
            UPDATE reference_value_types SET {", ".join(cols)}
            WHERE id = %s
            RETURNING
                id, key, label, description, category, default_unit, value_data_type,
                validation_rules, sort_order, active, metadata, created_at
            """,
            tuple(vals),
        )
        row = cur.fetchone()
        if not row:
            raise HTTPException(404, "Typ nicht gefunden")
        return _serialize_type(r2d(row))


@router.delete("/{type_id}")
def admin_delete_reference_value_type(type_id: int, session: dict = Depends(require_admin)):
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            "SELECT COUNT(*) AS c FROM profile_reference_values WHERE reference_value_type_id = %s",
            (type_id,),
        )
        n = cur.fetchone()["c"]
        if n > 0:
            raise HTTPException(
                409,
                f"Es gibt noch {n} gespeicherte Referenzwert(e) zu diesem Typ. "
                "Bitte zuerst löschen oder den Typ deaktivieren (active = aus).",
            )
        cur.execute("DELETE FROM reference_value_types WHERE id = %s RETURNING id", (type_id,))
        if not cur.fetchone():
            raise HTTPException(404, "Typ nicht gefunden")
    return {"ok": True}
