"""Temperature daemon main loop."""

from __future__ import annotations

import logging
import os
import signal
import time
from datetime import datetime, timezone
from typing import Any

from .config import TempConfig
from .identity import (
    TUB_COUNT,
    classify_readings,
    evaluate_identity,
    load_probe_map,
    save_probe_map,
)
from .reader import read_all_f
from .redis_out import RedisPublisher

logger = logging.getLogger("hot_tub.temp")

_stop = False


def _handle_signal(signum: int, _frame: object) -> None:
    global _stop
    logger.info("Received signal %s, shutting down", signum)
    _stop = True


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


def _health_status(setup_state: str, ok_count: int, visible_count: int) -> str:
    if setup_state == "need_reinstall":
        return "error"
    if setup_state in ("need_hot_in", "need_water"):
        return "degraded"
    if visible_count == 0:
        return "error"
    if ok_count < TUB_COUNT:
        return "degraded"
    return "ok"


def build_sample(
    cfg: TempConfig,
) -> tuple[dict[str, Any], Any, dict[str, Any], dict[str, Any]]:
    from pathlib import Path

    path = cfg.probe_map_path
    setup_mode = cfg.setup_mode
    probes_f = read_all_f(cfg.devices_path)
    visible = list(probes_f.keys())
    pmap = load_probe_map(path)
    identity = evaluate_identity(visible, pmap, setup_mode=setup_mode)
    if identity.map_changed:
        save_probe_map(path, identity.probe_map)
        logger.info("Probe map updated: %s", identity.detail)

    labeled, tub_avg, hot_in_f, ok_count, fail_count, spread = classify_readings(
        probes_f, identity.probe_map
    )

    reading: dict[str, Any] = {
        "ts": _utc_now(),
        "setup_state": identity.setup_state,
        "probes": labeled,
        "tub_avg_f": tub_avg,
        "hot_in_f": hot_in_f,
        "outdoor_f": None,
        "ok_count": ok_count,
        "fail_count": fail_count,
        "spread_f": spread,
    }

    setup_payload = {
        "ts": reading["ts"],
        "state": identity.setup_state,
        "detail": identity.detail,
        "visible_ids": identity.visible_ids,
    }

    health_payload = {
        "service": "temps",
        "ts": reading["ts"],
        "status": _health_status(identity.setup_state, ok_count, len(visible)),
        "detail": identity.detail,
        "pid": os.getpid(),
    }

    return reading, identity, setup_payload, health_payload


def run_once(
    cfg: TempConfig,
    *,
    dry_run: bool = False,
    redis_pub: RedisPublisher | None = None,
) -> dict[str, Any]:
    reading, identity, setup_payload, health_payload = build_sample(cfg)
    logger.info(
        "sample setup=%s tub_avg=%s hot_in=%s ok=%s/%s visible=%s",
        identity.setup_state,
        reading["tub_avg_f"],
        reading["hot_in_f"],
        reading["ok_count"],
        TUB_COUNT,
        len(identity.visible_ids),
    )
    if dry_run:
        return reading
    pub = redis_pub or RedisPublisher(cfg.redis_url)
    try:
        pub.publish_reading(reading)
        pub.publish_setup(setup_payload)
        pub.publish_health(health_payload)
    finally:
        if redis_pub is None:
            pub.close()
    return reading


def run_loop(cfg: TempConfig, *, dry_run: bool = False) -> None:
    global _stop
    _stop = False
    signal.signal(signal.SIGTERM, _handle_signal)
    signal.signal(signal.SIGINT, _handle_signal)

    logger.info(
        "Temperature daemon starting (dry_run=%s interval=%ss setup_mode=%s)",
        dry_run,
        cfg.interval_s,
        cfg.setup_mode,
    )

    redis_pub: RedisPublisher | None = None
    if not dry_run:
        redis_pub = RedisPublisher(cfg.redis_url)

    try:
        while not _stop:
            loop_start = time.monotonic()
            try:
                run_once(cfg, dry_run=dry_run, redis_pub=redis_pub)
            except Exception:
                logger.exception("Error in poll cycle")
            elapsed = time.monotonic() - loop_start
            sleep_s = max(0.0, cfg.interval_s - elapsed)
            if sleep_s > 0 and not _stop:
                time.sleep(sleep_s)
    finally:
        if redis_pub is not None:
            redis_pub.close()
        logger.info("Temperature daemon stopped")
