"""Tests for feed staleness guard."""

import os
import time

os.environ.setdefault("HORIZON_API_KEY", "test-key-123")

from horizon.context import Context, FeedData, InventorySnapshot
from horizon.feed_guard import (
    FeedHealthReport,
    FeedHealthStatus,
    feed_guard,
)


def _make_ctx(feeds_config=None, params=None):
    feeds = {}
    if feeds_config:
        for name, cfg in feeds_config.items():
            feeds[name] = FeedData(**cfg)
    return Context(
        feeds=feeds,
        inventory=InventorySnapshot(),
        params=params if params is not None else {},
    )


class TestFeedHealthStatus:
    def test_dataclass_defaults(self):
        s = FeedHealthStatus(name="test")
        assert s.name == "test"
        assert s.is_stale is False
        assert s.age_secs == 0.0
        assert s.consecutive_stale_cycles == 0


class TestFeedHealthReport:
    def test_dataclass_defaults(self):
        r = FeedHealthReport()
        assert r.feeds == {}
        assert r.all_stale is False
        assert r.should_kill is False
        assert r.should_recover is False


class TestFeedGuard:
    def test_healthy_feeds(self):
        guard = feed_guard(stale_threshold=30.0)
        now = time.time()
        ctx = _make_ctx({"btc": {"price": 50000, "timestamp": now}})
        guard(ctx)
        report = ctx.params["feed_health"]
        assert isinstance(report, FeedHealthReport)
        assert report.all_stale is False
        assert report.should_kill is False

    def test_stale_feed_detected(self):
        guard = feed_guard(stale_threshold=5.0)
        old_ts = time.time() - 60
        ctx = _make_ctx({"btc": {"price": 50000, "timestamp": old_ts}})
        guard(ctx)
        report = ctx.params["feed_health"]
        assert report.feeds["btc"].is_stale is True
        assert report.feeds["btc"].consecutive_stale_cycles == 1

    def test_consecutive_stale_cycles_increment(self):
        guard = feed_guard(stale_threshold=5.0)
        old_ts = time.time() - 60
        for i in range(3):
            ctx = _make_ctx({"btc": {"price": 50000, "timestamp": old_ts}})
            guard(ctx)
        report = ctx.params["feed_health"]
        assert report.feeds["btc"].consecutive_stale_cycles == 3

    def test_stale_cycles_reset_on_healthy(self):
        guard = feed_guard(stale_threshold=5.0)
        old_ts = time.time() - 60
        # Stale cycle
        ctx = _make_ctx({"btc": {"price": 50000, "timestamp": old_ts}})
        guard(ctx)
        assert ctx.params["feed_health"].feeds["btc"].consecutive_stale_cycles == 1
        # Healthy cycle
        ctx = _make_ctx({"btc": {"price": 50000, "timestamp": time.time()}})
        guard(ctx)
        assert ctx.params["feed_health"].feeds["btc"].consecutive_stale_cycles == 0

    def test_all_stale_triggers_kill(self):
        guard = feed_guard(stale_threshold=1.0, kill_threshold=0.0)
        old_ts = time.time() - 60
        ctx = _make_ctx({"btc": {"price": 50000, "timestamp": old_ts}})
        guard(ctx)
        report = ctx.params["feed_health"]
        assert report.all_stale is True
        assert report.should_kill is True

    def test_kill_not_triggered_below_threshold(self):
        guard = feed_guard(stale_threshold=1.0, kill_threshold=9999.0)
        old_ts = time.time() - 60
        ctx = _make_ctx({"btc": {"price": 50000, "timestamp": old_ts}})
        guard(ctx)
        report = ctx.params["feed_health"]
        assert report.all_stale is True
        assert report.should_kill is False

    def test_recovery_after_kill(self):
        guard = feed_guard(stale_threshold=1.0, kill_threshold=0.0)
        old_ts = time.time() - 60
        # Trigger kill
        ctx = _make_ctx({"btc": {"price": 50000, "timestamp": old_ts}})
        guard(ctx)
        assert ctx.params["feed_health"].should_kill is True
        # Recover
        ctx = _make_ctx({"btc": {"price": 50000, "timestamp": time.time()}})
        guard(ctx)
        report = ctx.params["feed_health"]
        assert report.should_recover is True
        assert report.should_kill is False

    def test_no_false_recover(self):
        """should_recover is only True after a kill was active."""
        guard = feed_guard(stale_threshold=30.0)
        ctx = _make_ctx({"btc": {"price": 50000, "timestamp": time.time()}})
        guard(ctx)
        assert ctx.params["feed_health"].should_recover is False

    def test_critical_feeds_subset(self):
        guard = feed_guard(stale_threshold=1.0, kill_threshold=0.0, critical_feeds=["btc"])
        old_ts = time.time() - 60
        ctx = _make_ctx({
            "btc": {"price": 50000, "timestamp": old_ts},
            "eth": {"price": 3000, "timestamp": time.time()},
        })
        guard(ctx)
        report = ctx.params["feed_health"]
        # Only btc is critical, and it's stale
        assert report.all_stale is True
        assert report.should_kill is True
        # eth is not monitored
        assert "eth" not in report.feeds

    def test_partial_stale_no_kill(self):
        guard = feed_guard(stale_threshold=1.0, kill_threshold=0.0)
        old_ts = time.time() - 60
        ctx = _make_ctx({
            "btc": {"price": 50000, "timestamp": old_ts},
            "eth": {"price": 3000, "timestamp": time.time()},
        })
        guard(ctx)
        report = ctx.params["feed_health"]
        assert report.all_stale is False
        assert report.should_kill is False

    def test_missing_feed_is_stale(self):
        guard = feed_guard(stale_threshold=5.0, critical_feeds=["btc", "missing"])
        ctx = _make_ctx({"btc": {"price": 50000, "timestamp": time.time()}})
        guard(ctx)
        report = ctx.params["feed_health"]
        assert report.feeds["missing"].is_stale is True

    def test_no_feeds_no_kill(self):
        guard = feed_guard()
        ctx = _make_ctx()
        guard(ctx)
        report = ctx.params["feed_health"]
        assert report.should_kill is False

    def test_function_name(self):
        guard = feed_guard()
        assert guard.__name__ == "feed_guard"
