"""Feed staleness detection and auto-kill switch for hz.run().

Monitors feed health and triggers kill switch when all critical feeds go stale.
Automatically recovers when feeds come back.

Usage:
    hz.run(
        pipeline=[
            hz.feed_guard(stale_threshold=30.0, kill_threshold=60.0),
            model,
            quoter,
        ],
        ...
    )
"""

from __future__ import annotations

import time
from dataclasses import dataclass, field
from typing import Callable

from horizon.context import Context


@dataclass
class FeedHealthStatus:
    """Health status for a single feed."""

    name: str
    is_stale: bool = False
    age_secs: float = 0.0
    consecutive_stale_cycles: int = 0
    last_healthy_time: float = 0.0


@dataclass
class FeedHealthReport:
    """Aggregated feed health report."""

    feeds: dict[str, FeedHealthStatus] = field(default_factory=dict)
    all_stale: bool = False
    all_stale_duration_secs: float = 0.0
    should_kill: bool = False
    should_recover: bool = False


def feed_guard(
    stale_threshold: float = 30.0,
    kill_threshold: float = 60.0,
    critical_feeds: list[str] | None = None,
) -> Callable[[Context], None]:
    """Create a feed staleness monitor pipeline function.

    Injects ``ctx.params["feed_health"]`` with a FeedHealthReport each cycle.
    When all critical feeds are stale for longer than ``kill_threshold``,
    sets ``should_kill = True``. When feeds recover after a kill,
    sets ``should_recover = True``.

    Args:
        stale_threshold: Seconds before a feed is considered stale.
        kill_threshold: Seconds of all-stale before triggering kill switch.
        critical_feeds: Feed names that must be healthy. None = all feeds.

    Returns:
        Pipeline function: (Context) -> None
    """
    # Closure state
    _stale_cycles: dict[str, int] = {}
    _last_healthy: dict[str, float] = {}
    _all_stale_since: float = 0.0
    _kill_active: bool = False

    def _guard(ctx: Context) -> None:
        nonlocal _all_stale_since, _kill_active

        now = time.time()
        report = FeedHealthReport()

        # Determine which feeds to monitor
        monitored = critical_feeds if critical_feeds is not None else list(ctx.feeds.keys())

        for fname in monitored:
            fd = ctx.feeds.get(fname)
            status = FeedHealthStatus(name=fname)

            if fd is None:
                status.is_stale = True
                status.age_secs = float("inf")
                _stale_cycles[fname] = _stale_cycles.get(fname, 0) + 1
                status.consecutive_stale_cycles = _stale_cycles[fname]
                status.last_healthy_time = _last_healthy.get(fname, 0.0)
            else:
                is_stale = fd.is_stale(stale_threshold)
                status.is_stale = is_stale
                status.age_secs = (now - fd.timestamp) if fd.timestamp > 0 else float("inf")

                if is_stale:
                    _stale_cycles[fname] = _stale_cycles.get(fname, 0) + 1
                else:
                    _stale_cycles[fname] = 0
                    _last_healthy[fname] = now

                status.consecutive_stale_cycles = _stale_cycles[fname]
                status.last_healthy_time = _last_healthy.get(fname, 0.0)

            report.feeds[fname] = status

        # Check if ALL critical feeds are stale
        if monitored:
            all_stale = all(report.feeds[f].is_stale for f in monitored if f in report.feeds)
        else:
            all_stale = False

        report.all_stale = all_stale

        if all_stale:
            if _all_stale_since == 0.0:
                _all_stale_since = now
            report.all_stale_duration_secs = now - _all_stale_since

            if report.all_stale_duration_secs >= kill_threshold:
                report.should_kill = True
                _kill_active = True
        else:
            if _kill_active:
                report.should_recover = True
                _kill_active = False
            _all_stale_since = 0.0
            report.all_stale_duration_secs = 0.0

        ctx.params["feed_health"] = report

    _guard.__name__ = "feed_guard"
    return _guard
