"""GET /sites/{site_id}/configuration – read-only souhrn konfigurace lokality."""

from __future__ import annotations

import json
from datetime import datetime, timezone
from typing import Annotated, Any

import asyncpg
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, ConfigDict, Field

from app.db_json import fetch_json
from app.deps import get_pg_pool

router = APIRouter(prefix="/sites/{site_id}", tags=["sites"])


class PvForecastCalibrationPatch(BaseModel):
    """Částečná úprava `ems.site_pv_forecast_calibration`. Vynechané klíče = beze změny."""

    model_config = ConfigDict(extra="forbid")

    delta_learn_min_ts: datetime | None = None
    pv_curtailment_policy_effective_from: datetime | None = None
    top_n_days: int | None = Field(default=None, ge=0, le=31)
    non_top_day_factor: float | None = Field(default=None, ge=0, le=1)
    day_weight_gamma: float | None = Field(default=None, ge=0.25, le=8)
    half_life_days: float | None = Field(default=None, ge=1, le=90)
    threshold_w: int | None = Field(default=None, ge=0, le=10_000)


class InverterModbusCurrentCapsBody(BaseModel):
    """Tvrdý strop proudu pro zápis Deye reg 108/109 (A); NULL ve JSONu = smaž strop v DB."""

    deye_register_max_charge_a: int | None = Field(
        default=None,
        ge=0,
        le=640,
        description="None při vynechání klíče = nezměnit; explicitní null = smazat strop",
    )
    deye_register_max_discharge_a: int | None = Field(
        default=None,
        ge=0,
        le=640,
        description="Jako u nabíjení",
    )


def _iso_utc_from_cfg(val: Any) -> str | None:
    if val is None:
        return None
    if isinstance(val, str):
        return val
    if isinstance(val, datetime):
        dt = val
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=timezone.utc)
        return dt.astimezone(timezone.utc).isoformat()
    return str(val)


@router.get("/configuration")
async def get_site_configuration(
    site_id: int,
    pool: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
) -> dict[str, Any]:
    async with pool.acquire() as conn:
        raw = await fetch_json(
            conn,
            "select ems.fn_site_configuration($1::int)",
            site_id,
        )
    if raw is None:
        raise HTTPException(status_code=404, detail="Site not found")
    if not isinstance(raw, dict):
        raw = json.loads(raw)
    op = raw.get("operational")
    if isinstance(op, dict):
        op = dict(op)
        op["heartbeat_last_seen"] = _iso_utc_from_cfg(op.get("heartbeat_last_seen"))
        op["active_plan_created_at"] = _iso_utc_from_cfg(op.get("active_plan_created_at"))
        raw["operational"] = op
    lat = raw.get("site", {}).get("latitude") if isinstance(raw.get("site"), dict) else None
    lon = raw.get("site", {}).get("longitude") if isinstance(raw.get("site"), dict) else None
    if isinstance(raw.get("site"), dict):
        site = dict(raw["site"])
        site["latitude"] = float(lat) if lat is not None else None
        site["longitude"] = float(lon) if lon is not None else None
        raw["site"] = site
    return raw


@router.patch("/configuration/pv-forecast-calibration")
async def patch_pv_forecast_calibration(
    site_id: int,
    body: PvForecastCalibrationPatch,
    pool: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
) -> dict[str, Any]:
    """Aktualizace kalibrace PV delty (`ems.site_pv_forecast_calibration`)."""
    updates = body.model_dump(exclude_unset=True)
    if not updates:
        raise HTTPException(status_code=400, detail="No fields to update")
    if updates.get("delta_learn_min_ts") is None and "delta_learn_min_ts" in updates:
        raise HTTPException(
            status_code=422,
            detail="delta_learn_min_ts cannot be null (column is NOT NULL)",
        )

    allowed = {
        "delta_learn_min_ts",
        "pv_curtailment_policy_effective_from",
        "top_n_days",
        "non_top_day_factor",
        "day_weight_gamma",
        "half_life_days",
        "threshold_w",
    }
    bad = set(updates) - allowed
    if bad:
        raise HTTPException(status_code=400, detail=f"Unknown fields: {sorted(bad)}")

    cols = list(updates.keys())
    set_parts: list[str] = []
    args: list[Any] = [site_id]
    for i, col in enumerate(cols, start=2):
        set_parts.append(f"{col} = ${i}")
        args.append(updates[col])
    set_sql = ", ".join(set_parts) + ", updated_at = now()"

    async with pool.acquire() as conn:
        site_ok = await conn.fetchval(
            "SELECT EXISTS(SELECT 1 FROM ems.site WHERE id = $1)", site_id
        )
        if not site_ok:
            raise HTTPException(status_code=404, detail="Site not found")
        n = await conn.execute(
            f"""
            UPDATE ems.site_pv_forecast_calibration
            SET {set_sql}
            WHERE site_id = $1
            """,
            *args,
        )
        if n == "UPDATE 0":
            raise HTTPException(
                status_code=404,
                detail="PV forecast calibration row missing; run migration V057",
            )
        await conn.execute(
            # p_force=true: uživatel právě změnil kalibraci — throttle 6 h nesmí
            # nechat starou cache (čtenář ji od HOTFIXu 2/2 vrací bez přepočtu)
            "select ems.fn_refresh_site_pv_delta_profile_cache($1::int, true)",
            site_id,
        )
        row = await conn.fetchrow(
            """
            SELECT to_jsonb(c.*) AS j
            FROM ems.site_pv_forecast_calibration c
            WHERE c.site_id = $1
            """,
            site_id,
        )
    raw = row["j"] if row else {}
    if not isinstance(raw, dict):
        raw = json.loads(raw)
    return raw


@router.patch("/inverters/{inverter_id}/modbus-current-caps")
async def patch_inverter_modbus_current_caps(
    site_id: int,
    inverter_id: int,
    body: InverterModbusCurrentCapsBody,
    pool: Annotated[asyncpg.Pool, Depends(get_pg_pool)],
) -> dict[str, Any]:
    """
    Nastavení `deye_register_max_charge_a` / `deye_register_max_discharge_a` na `ems.asset_inverter`.
    """
    updates = body.model_dump(exclude_unset=True)
    if not updates:
        raise HTTPException(
            status_code=400,
            detail="Send at least one of: deye_register_max_charge_a, deye_register_max_discharge_a",
        )
    patch: dict[str, Any] = {}
    if "deye_register_max_charge_a" in updates:
        patch["deye_register_max_charge_a"] = updates["deye_register_max_charge_a"]
    if "deye_register_max_discharge_a" in updates:
        patch["deye_register_max_discharge_a"] = updates["deye_register_max_discharge_a"]

    async with pool.acquire() as conn:
        raw = await fetch_json(
            conn,
            "select ems.fn_inverter_modbus_caps_patch($1::int, $2::int, $3::jsonb)",
            site_id,
            inverter_id,
            json.dumps(patch),
        )
    if not isinstance(raw, dict):
        raw = json.loads(raw)
    if not raw.get("ok"):
        if raw.get("error") == "not_found":
            raise HTTPException(status_code=404, detail="Inverter not found for this site")
        raise HTTPException(status_code=400, detail=raw.get("error", "patch_failed"))
    return {
        "inverter_id": int(raw["inverter_id"]),
        "code": raw["code"],
        "deye_register_max_charge_a": raw.get("deye_register_max_charge_a"),
        "deye_register_max_discharge_a": raw.get("deye_register_max_discharge_a"),
    }
