"""Simulate tub/hot-in temps from Control state; publish like the real temp daemon."""

from __future__ import annotations

import json
import os
import signal
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any

try:
    import redis
except ImportError:
    redis = None  # type: ignore[assignment]

from temp.redis_out import RedisPublisher

KEY_CONTROL_LATEST = "control:latest"

DEFAULT_TUB_F = 70.0
DEFAULT_HOT_F = 70.0
HOT_ACTIVE_F = 130.0
STEP_F = 5.0
TUB_MAX_F = 140.0

TUB_PROBE_IDS = (
    "28-sim-tub1",
    "28-sim-tub2",
    "28-sim-tub3",
    "28-sim-tub4",
)
HOT_PROBE_ID = "28-sim-hot"

HEATING_PHASES = frozenset({"startup_1", "startup_2", "startup_3", "heating"})

_stop = False


def _utc_now() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def _handle_signal(signum: int, _frame: object) -> None:
    global _stop
    print(f"\nSignal {signum}, stopping.")
    _stop = True


@dataclass
class SimState:
    tub_avg_f: float = DEFAULT_TUB_F
    hot_in_f: float = DEFAULT_HOT_F


def fetch_control_state(redis_url: str) -> dict[str, Any] | None:
    if redis is None:
        raise RuntimeError("redis package not installed (apt: python3-redis)")
    client = redis.from_url(redis_url, decode_responses=True)
    try:
        raw = client.get(KEY_CONTROL_LATEST)
    finally:
        client.close()
    if not raw:
        return None
    try:
        data = json.loads(raw)
    except json.JSONDecodeError:
        return None
    return data if isinstance(data, dict) else None


def should_heat_up(control: dict[str, Any] | None) -> tuple[bool, str, str]:
    """Return (heat_up, heat_phase, heat_pump) for display."""
    if control is None:
        return False, "—", "—"
    phase = str(control.get("heat_phase") or "idle")
    pump = str(control.get("heat_pump") or "off")
    if phase in HEATING_PHASES and pump != "off":
        return True, phase, pump
    return False, phase, pump


def tick_sim(state: SimState, heat_up: bool) -> None:
    if heat_up:
        state.hot_in_f = HOT_ACTIVE_F
        state.tub_avg_f = min(state.tub_avg_f + STEP_F, TUB_MAX_F)
    else:
        state.tub_avg_f = max(DEFAULT_TUB_F, state.tub_avg_f - STEP_F)
        state.hot_in_f = state.tub_avg_f


def build_payloads(state: SimState) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
    ts = _utc_now()
    probes = {pid: state.tub_avg_f for pid in TUB_PROBE_IDS}
    probes[HOT_PROBE_ID] = state.hot_in_f

    reading = {
        "ts": ts,
        "setup_state": "ready",
        "probes": probes,
        "roles": {
            "hot_in": state.hot_in_f,
            "tub1": state.tub_avg_f,
            "tub2": state.tub_avg_f,
            "tub3": state.tub_avg_f,
            "tub4": state.tub_avg_f,
        },
        "tub_avg_f": state.tub_avg_f,
        "hot_in_f": state.hot_in_f,
        "outdoor_f": None,
        "ok_count": 4,
        "fail_count": 0,
        "spread_f": 0.0,
    }
    setup_payload = {
        "ts": ts,
        "state": "ready",
        "detail": "temp_sim diagnostic simulator",
        "visible_ids": list(TUB_PROBE_IDS) + [HOT_PROBE_ID],
        "setup_active": False,
    }
    health_payload = {
        "service": "temps",
        "ts": ts,
        "status": "ok",
        "detail": "temp_sim (diagnostic simulator)",
        "pid": os.getpid(),
    }
    return reading, setup_payload, health_payload


def run_loop(*, redis_url: str, interval_s: float) -> None:
    global _stop
    _stop = False
    signal.signal(signal.SIGTERM, _handle_signal)
    signal.signal(signal.SIGINT, _handle_signal)

    state = SimState()
    pub = RedisPublisher(redis_url)

    print(
        f"temp_sim starting: interval={interval_s}s redis={redis_url} "
        f"defaults tub={DEFAULT_TUB_F}F hot={DEFAULT_HOT_F}F"
    )
    print("Stop with Ctrl+C. Do not run while hot-tub-temps.service is active.\n")

    try:
        while not _stop:
            loop_start = time.monotonic()
            control = fetch_control_state(redis_url)
            heat_up, phase, pump = should_heat_up(control)
            mode = "HEAT" if heat_up else "COOL"
            tick_sim(state, heat_up)

            reading, setup_payload, health_payload = build_payloads(state)
            ok_read = pub.publish_reading(reading)
            ok_setup = pub.publish_setup(setup_payload)
            ok_health = pub.publish_health(health_payload)
            redis_ok = ok_read and ok_setup and ok_health

            print(
                f"{reading['ts']}  {mode:4}  phase={phase:12} pump={pump:3}  "
                f"tub={state.tub_avg_f:5.1f}F  hot_in={state.hot_in_f:5.1f}F  "
                f"redis={'OK' if redis_ok else 'FAIL'}"
            )

            elapsed = time.monotonic() - loop_start
            sleep_s = max(0.0, interval_s - elapsed)
            if sleep_s > 0 and not _stop:
                time.sleep(sleep_s)
    finally:
        pub.close()
        print("temp_sim stopped.")
