"""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]],  # 0 overlaps, 3 gaps
            "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]],  # 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_suppressed_when_kintsugi_healing(self):
        """Today gap exists but kintsugi healing -> NO LOUD alert."""
        client = _make_client({
            "is_done":      [[0]],
            "GROUP BY symbol": [["BTCUSDT", 250, 0, 1]],
            "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)

        mock_send.assert_not_called()
        assert results.get("stathera_today_gap_alert") is not 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]],
            "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
