"""Tests for heal operation scoping (HEAL-01, HEAL-02).

HEAL-01: _heal_day_boundary_gaps() only queries today's UTC bars
HEAL-02: _check_and_heal_overlaps() call site uses valid_pairs
"""

from __future__ import annotations

import inspect
import re
import textwrap
from unittest.mock import MagicMock, patch


class TestHealDayBoundaryGapsScoping:
    """HEAL-01: _heal_day_boundary_gaps query must include today() date filter."""

    @patch("opendeviationbar.clickhouse.stathera_query.per_pair_filter")
    @patch("opendeviationbar.ouroboros.get_operational_ouroboros_mode", return_value="day")
    @patch("opendeviationbar.symbol_registry.filter_pairs_by_registry", side_effect=lambda x: x)
    @patch("opendeviationbar.clickhouse.cache.OpenDeviationBarCache")
    def test_heal_query_contains_today_filter(
        self,
        mock_cache_cls: MagicMock,
        mock_filter: MagicMock,
        mock_ouroboros: MagicMock,
        mock_per_pair_filter: MagicMock,
    ) -> None:
        """The Stathera query must contain 'toDate(open_time_ms / 1000) = today()'."""
        # Setup: per_pair_filter returns a WHERE clause
        mock_per_pair_filter.return_value = (
            "WHERE symbol = {symbol:String} "
            "AND threshold_decimal_bps = {threshold:UInt32} "
            "AND ouroboros_mode = {ouroboros:String} "
            "AND first_agg_trade_id > 0 AND last_agg_trade_id > 0"
        )

        mock_cache = MagicMock()
        mock_cache_cls.return_value = mock_cache
        mock_result = MagicMock()
        mock_result.result_rows = []
        mock_cache.client.query.return_value = mock_result

        engine = MagicMock()
        valid_pairs = {("BTCUSDT", 250)}

        from opendeviationbar.sidecar import _heal_day_boundary_gaps

        _heal_day_boundary_gaps(engine, valid_pairs)

        # Verify the query was called and contains today() filter
        assert mock_cache.client.query.call_count == 1
        query_sql = mock_cache.client.query.call_args[0][0]
        assert "toDate(open_time_ms / 1000) = today()" in query_sql

    @patch("opendeviationbar.clickhouse.stathera_query.per_pair_filter")
    @patch("opendeviationbar.ouroboros.get_operational_ouroboros_mode", return_value="day")
    @patch("opendeviationbar.symbol_registry.filter_pairs_by_registry", side_effect=lambda x: x)
    @patch("opendeviationbar.clickhouse.cache.OpenDeviationBarCache")
    def test_heal_query_preserves_symbol_threshold_ouroboros_filter(
        self,
        mock_cache_cls: MagicMock,
        mock_filter: MagicMock,
        mock_ouroboros: MagicMock,
        mock_per_pair_filter: MagicMock,
    ) -> None:
        """The query must still filter by symbol, threshold, and ouroboros."""
        mock_per_pair_filter.return_value = (
            "WHERE symbol = {symbol:String} "
            "AND threshold_decimal_bps = {threshold:UInt32} "
            "AND ouroboros_mode = {ouroboros:String} "
            "AND first_agg_trade_id > 0 AND last_agg_trade_id > 0"
        )

        mock_cache = MagicMock()
        mock_cache_cls.return_value = mock_cache
        mock_result = MagicMock()
        mock_result.result_rows = []
        mock_cache.client.query.return_value = mock_result

        engine = MagicMock()
        valid_pairs = {("ETHUSDT", 500)}

        from opendeviationbar.sidecar import _heal_day_boundary_gaps

        _heal_day_boundary_gaps(engine, valid_pairs)

        query_sql = mock_cache.client.query.call_args[0][0]
        # per_pair_filter content should be present
        assert "symbol" in query_sql
        assert "threshold" in query_sql
        assert "ouroboros" in query_sql
        # Parameters must include the correct symbol/threshold
        params = mock_cache.client.query.call_args[1]["parameters"]
        assert params["symbol"] == "ETHUSDT"
        assert params["threshold"] == 500
        assert params["ouroboros"] == "day"


class TestOverlapHealUsesValidPairs:
    """HEAL-02: _check_and_heal_overlaps call site must use valid_pairs."""

    def test_overlap_heal_call_site_uses_valid_pairs_not_cartesian(self) -> None:
        """The overlap heal block must pass valid_pairs, not config.symbols x config.thresholds."""
        from opendeviationbar import sidecar

        source = inspect.getsource(sidecar)

        # Find the overlap heal block (marked by "v1.1: Overlap self-heal")
        heal_block_match = re.search(
            r"v1\.1: Overlap self-heal.*?_check_and_heal_overlaps\([^)]+\)",
            source,
            re.DOTALL,
        )
        assert heal_block_match is not None, (
            "Could not find overlap heal block with _check_and_heal_overlaps call"
        )
        heal_block = heal_block_match.group(0)

        # Must NOT contain cartesian product pattern
        assert "config.symbols" not in heal_block, (
            "Overlap heal call site still uses config.symbols (should use valid_pairs)"
        )
        assert "config.thresholds" not in heal_block, (
            "Overlap heal call site still uses config.thresholds (should use valid_pairs)"
        )

        # Must contain valid_pairs
        assert "valid_pairs" in heal_block, (
            "Overlap heal call site does not use valid_pairs"
        )
