"""Tests for unified Stathera heartbeat check (TELEM-01, TELEM-04)."""

from __future__ import annotations

import os
from unittest.mock import MagicMock, patch


def _make_client(queries: dict[str, list[list]]) -> MagicMock:
    """Create a mock ClickHouse client returning canned results per query substring.

    ``queries`` maps a substring (found in the SQL) to a list of rows.
    Each call to ``client.query(sql)`` checks the substrings in order and
    returns the first match.
    """
    client = MagicMock()

    def _query(sql: str, **_kw: object) -> MagicMock:
        result = MagicMock()
        for substr, rows in queries.items():
            if substr in sql:
                result.result_rows = rows
                return result
        # Default: empty result
        result.result_rows = []
        return result

    client.query.side_effect = _query
    return client


class TestCollectStatheraUnified:
    """Test 1-2: unified per-symbol output, no intra-day/adjacent-day keys."""

    def test_clean_no_anomalies(self):
        """Zero gaps/overlaps -> stathera_per_symbol empty, stathera_gaps=True, stathera_overlaps=True."""
        client = _make_client({
            "is_done":      [[0]],       # pending mutations
            "GROUP BY symbol": [],       # per-symbol query returns nothing
            "today()": [],          # today-gap query returns nothing
            "violations":   [[0]],       # cross-midnight
        })
        results: dict = {}

        with patch("heartbeat.checks.stathera.load_stathera_counts", return_value={}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        assert results["stathera_per_symbol"] == {}
        assert results["stathera_gaps"] is True
        assert results["stathera_overlaps"] is True
        # No intra_day or adjacent_day keys
        assert "stathera_intra_day_gaps" not in results
        assert "stathera_adjacent_day_gaps" not in results

    def test_gaps_in_one_symbol(self):
        """Gaps in BTCUSDT@250 -> stathera_per_symbol populated, stathera_gaps reflects anomaly."""
        client = _make_client({
            "is_done":      [[0]],
            "GROUP BY symbol": [["BTCUSDT", 250, 0, 3, 1711000000000]],  # 0 overlaps, 3 gaps, latest_open_ms
            "today()": [],
            "violations":   [[0]],
            # top-5 gap pairs
            "tid_delta > 0 AND rn > 1\n            GROUP BY": [["BTCUSDT@250", 3]],
            # fingerprints
            "tid_delta > 0 AND rn > 1\n                ORDER BY": [],
        })
        results: dict = {"kintsugi": False}

        with patch("heartbeat.checks.stathera.load_stathera_counts", return_value={"overlap_count": 0, "gap_count": 0}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        assert "BTCUSDT@250" in results["stathera_per_symbol"]
        assert results["stathera_per_symbol"]["BTCUSDT@250"]["gaps"] == 3
        assert results["stathera_per_symbol"]["BTCUSDT@250"]["overlaps"] == 0
        assert results["stathera_gap_count"] == "3"
        # No intra-day keys
        assert "stathera_intra_day_gaps" not in results
        assert "stathera_adjacent_day_gaps" not in results


class TestTodayGapLoudAlert:
    """Test 3-5: LOUD alert fires for today's new gaps."""

    def test_today_gap_sends_loud_alert(self):
        """New gap in today's bars + gap_delta > 0 + not healing -> LOUD alert."""
        client = _make_client({
            "is_done":      [[0]],
            "GROUP BY symbol": [["BTCUSDT", 250, 0, 1, 1711000000000]],  # 1 gap total
            "today()": [["BTCUSDT", 250, 1000, 1002, 1]],  # sym, thr, prev_last, first, delta
            "violations":   [[0]],
            "tid_delta > 0 AND rn > 1\n            GROUP BY": [["BTCUSDT@250", 1]],
            "tid_delta > 0 AND rn > 1\n                ORDER BY": [],
        })
        results: dict = {"kintsugi": False}

        with patch("heartbeat.checks.stathera.load_stathera_counts", return_value={"overlap_count": 0, "gap_count": 0}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"), \
             patch("heartbeat.checks.stathera.send_telegram", return_value=1) as mock_send, \
             patch.dict(os.environ, {"OPENDEVIATIONBAR_TELEGRAM_TOKEN": "tok", "OPENDEVIATIONBAR_TELEGRAM_CHAT_ID": "123"}):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        mock_send.assert_called_once()
        call_kwargs = mock_send.call_args
        assert call_kwargs.kwargs.get("silent") is False or (len(call_kwargs) > 1 and call_kwargs[1].get("silent") is False)
        assert results.get("stathera_today_gap_alert") is True

    def test_today_gap_loud_alert_fires_even_when_kintsugi_healing(self):
        """#295 P0-3: Today gap fires LOUD alert even when kintsugi_healing=True.

        Kintsugi cannot heal today (#280 today-guard), so today's gap LOUD
        alert must NEVER be suppressed by kintsugi_healing state.
        """
        client = _make_client({
            "is_done":      [[0]],
            "GROUP BY symbol": [["BTCUSDT", 250, 0, 1, 1711000000000]],
            "AS today_gaps": [[2, 0]],  # 2 today gaps, 0 today overlaps
            "today()": [["BTCUSDT", 250, 1000, 1002, 1]],
            "violations":   [[0]],
            "tid_delta > 0 AND rn > 1\n            GROUP BY": [["BTCUSDT@250", 1]],
            "tid_delta > 0 AND rn > 1\n                ORDER BY": [],
        })
        results: dict = {"kintsugi": True, "kintsugi_stuck": True}

        with patch("heartbeat.checks.stathera.load_stathera_counts",
                    return_value={"overlap_count": 0, "gap_count": 0}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"), \
             patch("heartbeat.checks.stathera.send_telegram", return_value=1) as mock_send, \
             patch.dict(os.environ, {"OPENDEVIATIONBAR_TELEGRAM_TOKEN": "tok", "OPENDEVIATIONBAR_TELEGRAM_CHAT_ID": "123"}):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        # LOUD alert must fire even though kintsugi is healing
        mock_send.assert_called_once()
        assert results.get("stathera_today_gap_alert") is True

    def test_today_gap_not_suppressed_when_kintsugi_healing(self):
        """#295 P0-3: Today gap exists + kintsugi healing -> LOUD alert FIRES.

        Kintsugi can't fix today (#280 today-guard). Previous behavior
        (suppressing LOUD alert when kintsugi_healing=True) was incorrect.
        """
        client = _make_client({
            "is_done":      [[0]],
            "GROUP BY symbol": [["BTCUSDT", 250, 0, 1, 1711000000000]],
            "AS today_gaps": [[1, 0]],  # 1 today gap
            "today()": [["BTCUSDT", 250, 1000, 1002, 1]],
            "violations":   [[0]],
            "tid_delta > 0 AND rn > 1\n            GROUP BY": [["BTCUSDT@250", 1]],
            "tid_delta > 0 AND rn > 1\n                ORDER BY": [],
        })
        results: dict = {"kintsugi": True, "kintsugi_stuck": True}

        with patch("heartbeat.checks.stathera.load_stathera_counts", return_value={"overlap_count": 0, "gap_count": 0}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"), \
             patch("heartbeat.checks.stathera.send_telegram", return_value=1) as mock_send, \
             patch.dict(os.environ, {"OPENDEVIATIONBAR_TELEGRAM_TOKEN": "tok", "OPENDEVIATIONBAR_TELEGRAM_CHAT_ID": "123"}):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        # LOUD alert must fire — kintsugi cannot fix today
        mock_send.assert_called_once()
        assert results.get("stathera_today_gap_alert") is True

    def test_loud_alert_contains_gap_details(self):
        """LOUD alert message contains exact symbol, threshold, tid_delta, trade ID range."""
        client = _make_client({
            "is_done":      [[0]],
            "GROUP BY symbol": [["ETHUSDT", 500, 0, 2, 1711000000000]],
            "today()": [
                ["ETHUSDT", 500, 5000, 5003, 2],  # sym, thr, prev_last=5000, first=5003, delta=2
            ],
            "violations":   [[0]],
            "tid_delta > 0 AND rn > 1\n            GROUP BY": [["ETHUSDT@500", 2]],
            "tid_delta > 0 AND rn > 1\n                ORDER BY": [],
        })
        results: dict = {"kintsugi": False}

        with patch("heartbeat.checks.stathera.load_stathera_counts", return_value={"overlap_count": 0, "gap_count": 0}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"), \
             patch("heartbeat.checks.stathera.send_telegram", return_value=1) as mock_send, \
             patch.dict(os.environ, {"OPENDEVIATIONBAR_TELEGRAM_TOKEN": "tok", "OPENDEVIATIONBAR_TELEGRAM_CHAT_ID": "123"}):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        msg = mock_send.call_args[0][2]  # positional arg: token, chat_id, message
        assert "ETHUSDT" in msg
        assert "500" in msg
        assert "5000" in msg
        assert "5003" in msg
        assert "tid_delta=" in msg


class TestTodayAnomalyAwareness:
    """#295 P0-3: Today anomalies never suppressed by kintsugi_healing."""

    def test_today_gaps_with_kintsugi_healing(self):
        """Today gaps + kintsugi healing -> stathera_today_gaps=False, stathera_gaps=True (suppressed)."""
        client = _make_client({
            "is_done":      [[0]],       # no pending mutations
            "GROUP BY symbol": [         # per-symbol: gaps exist
                ["BTCUSDT", 250, 3, 0, 1711000000000],
            ],
            "AS today_gaps": [[2, 0]],   # 2 today gaps, 0 today overlaps
            "today()": [                 # today-gap detail rows for LOUD alert
                ["BTCUSDT", 250, 100, 103, 2],
            ],
            "violations": [[0]],         # no cross-midnight
            "tid_delta > 0 AND rn > 1\n            GROUP BY": [["BTCUSDT@250", 3]],
            "tid_delta > 0 AND rn > 1\n                ORDER BY": [],
        })
        results: dict = {"kintsugi": True, "kintsugi_stuck": True}

        with patch("heartbeat.checks.stathera.load_stathera_counts",
                    return_value={"gap_count": 0, "overlap_count": 0}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"), \
             patch("heartbeat.checks.stathera.send_telegram", return_value=1), \
             patch.dict(os.environ, {"OPENDEVIATIONBAR_TELEGRAM_TOKEN": ""}):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        # Today keys are NEVER suppressed
        assert results["stathera_today_gaps"] is False
        assert results["stathera_today_overlaps"] is True
        # Historical gaps suppressed by kintsugi_healing
        assert results["stathera_gaps"] is True

    def test_historical_gaps_only_with_kintsugi_healing(self):
        """Historical gaps + kintsugi healing, no today anomalies -> all healthy."""
        client = _make_client({
            "is_done":      [[0]],
            "GROUP BY symbol": [
                ["BTCUSDT", 250, 5, 0, 1711000000000],
            ],
            "AS today_gaps": [[0, 0]],   # 0 today gaps, 0 today overlaps
            "today()": [],
            "violations": [[0]],
            "tid_delta > 0 AND rn > 1\n            GROUP BY": [["BTCUSDT@250", 5]],
            "tid_delta > 0 AND rn > 1\n                ORDER BY": [],
        })
        results: dict = {"kintsugi": True, "kintsugi_stuck": True}

        with patch("heartbeat.checks.stathera.load_stathera_counts",
                    return_value={"gap_count": 0, "overlap_count": 0}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"), \
             patch.dict(os.environ, {"OPENDEVIATIONBAR_TELEGRAM_TOKEN": ""}):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        assert results["stathera_today_gaps"] is True
        assert results["stathera_today_overlaps"] is True

    def test_zero_anomalies(self):
        """No anomalies at all -> all today keys True."""
        client = _make_client({
            "is_done":      [[0]],
            "GROUP BY symbol": [],
            "AS today_gaps": [[0, 0]],
            "today()": [],
            "violations": [[0]],
        })
        results: dict = {}

        with patch("heartbeat.checks.stathera.load_stathera_counts", return_value={}), \
             patch("heartbeat.checks.stathera.save_stathera_counts"), \
             patch.dict(os.environ, {"OPENDEVIATIONBAR_TELEGRAM_TOKEN": ""}):
            from heartbeat.checks.stathera import collect_stathera
            collect_stathera(client, results)

        assert results["stathera_today_gaps"] is True
        assert results["stathera_today_overlaps"] is True
