"""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

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 TestOverlapHealRemoved:
    """HEAL-02: _check_and_heal_overlaps was removed (#293/#295 i11)."""

    def test_overlap_heal_function_removed(self) -> None:
        """Verify _check_and_heal_overlaps is gone — pre-fill clean-slate DELETE
        eliminates the need for runtime overlap healing (#295 iteration 11)."""
        from opendeviationbar import sidecar

        assert not hasattr(sidecar, "_check_and_heal_overlaps"), (
            "_check_and_heal_overlaps should be removed (disabled since #293, "
            "eliminated by pre-fill clean-slate DELETE #295 i11)"
        )
