"""Safety interlocks before energizing heat."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any

from .config import ControlConfig
from .state import ControlState


@dataclass
class SafetyResult:
    allow_heat: bool
    reason: str | None = None
    trip: bool = False


def _parse_ts(ts: str | None) -> datetime | None:
    if not ts:
        return None
    try:
        if ts.endswith("Z"):
            return datetime.fromisoformat(ts.replace("Z", "+00:00"))
        return datetime.fromisoformat(ts)
    except ValueError:
        return None


def temps_fresh(reading: dict[str, Any] | None, cfg: ControlConfig, now: datetime | None = None) -> bool:
    if reading is None:
        return False
    ts = _parse_ts(reading.get("ts"))
    if ts is None:
        return False
    if ts.tzinfo is None:
        ts = ts.replace(tzinfo=timezone.utc)
    now = now or datetime.now(timezone.utc)
    age = (now - ts.astimezone(timezone.utc)).total_seconds()
    return age <= cfg.temps_stale_s


def check_heat_allowed(
    state: ControlState,
    cfg: ControlConfig,
    reading: dict[str, Any] | None,
    *,
    require_hot_in: bool = False,
    ignore_target: bool = False,
    now: datetime | None = None,
) -> SafetyResult:
    """Evaluate whether heat pump may be turned on."""
    # TODO: GPIO17 low-water float when hardware is added.

    if state.heat_phase == "error_startup":
        return SafetyResult(False, "latched error_startup")

    if reading is None or not temps_fresh(reading, cfg, now=now):
        return SafetyResult(False, "stale or missing temps", trip=state.heat_pump == "on")

    setup = reading.get("setup_state")
    if setup == "need_reinstall":
        return SafetyResult(False, "need_reinstall", trip=state.heat_pump == "on")

    tub = reading.get("tub_avg_f")
    if tub is None:
        return SafetyResult(False, "no tub_avg_f", trip=state.heat_pump == "on")

    state.tub_avg_f = float(tub)
    hot = reading.get("hot_in_f")
    state.hot_in_f = float(hot) if hot is not None else None

    if not ignore_target and state.tub_avg_f >= state.target_f:
        return SafetyResult(False, "at or above target")

    if require_hot_in and state.hot_in_f is None:
        return SafetyResult(False, "hot_in required for startup check")

    return SafetyResult(True)


def apply_safety_trip(state: ControlState, reason: str) -> None:
    state.heat_pump = "off"
    state.heat_phase = "error_safety"
    state.error = {"code": "safety", "detail": reason}
