"""Tests for optimized Aion DELETE strategy with skip-if-clean pre-check.

Validates that _aion_delete_after_resume() skips no-op partitions (where
max(last_agg_trade_id) <= resume_tid) and only issues DELETE IN PARTITION
for partitions that actually have bars beyond the resume point.
"""

from __future__ import annotations

import logging
from unittest.mock import MagicMock

import pytest


def _make_ch_cache(query_results: dict[str, list[tuple[int, int]]]) -> MagicMock:
    """Create a mock ch_cache with .client.query() and .client.command().

    Parameters
    ----------
    query_results
        Mapping of symbol -> list of (threshold_decimal_bps, max_tid) tuples.
        Simulates the result of the batch pre-check query per symbol.
    """
    cache = MagicMock()

    def _query_side_effect(sql: str) -> MagicMock:
        result = MagicMock()
        # Find which symbol was queried
        for sym, rows in query_results.items():
            if f"'{sym}'" in sql:
                result.result_rows = rows
                return result
        # Symbol not found -- empty result
        result.result_rows = []
        return result

    cache.client.query.side_effect = _query_side_effect
    cache.client.command.return_value = None
    return cache


def _thresholds_fn(sym: str) -> list[int]:
    """Return test thresholds for any symbol."""
    return [250, 500, 750, 1000]


class TestSkipNoopPartitions:
    """Test 1: No-op partitions are skipped entirely."""

    def test_skip_noop_partitions(self) -> None:
        """When max(last_agg_trade_id) <= resume_tid, zero DELETE commands issued."""
        from opendeviationbar.sidecar import _aion_delete_after_resume

        resume_tids = {"BTCUSDT": 100_000}
        # All thresholds have max_tid <= resume_tid (100_000)
        ch_cache = _make_ch_cache({
            "BTCUSDT": [(250, 99_000), (500, 100_000), (750, 95_000), (1000, 98_000)],
        })

        _aion_delete_after_resume(
            ch_cache, ["BTCUSDT"], resume_tids, _thresholds_fn
        )

        # Only query calls, zero DELETE commands
        delete_calls = [
            c for c in ch_cache.client.command.call_args_list
            if "DELETE" in str(c)
        ]
        assert len(delete_calls) == 0, f"Expected 0 DELETEs, got {len(delete_calls)}"


class TestDeleteOnlyNeededPartitions:
    """Test 2: Only partitions with bars after resume_tid get DELETE."""

    def test_delete_only_needed_partitions(self) -> None:
        """When 2 of 4 partitions have max_tid > resume_tid, exactly 2 DELETEs."""
        from opendeviationbar.sidecar import _aion_delete_after_resume

        resume_tids = {"BTCUSDT": 100_000}
        # Thresholds 250 and 750 have max_tid > resume_tid
        ch_cache = _make_ch_cache({
            "BTCUSDT": [(250, 100_001), (500, 99_999), (750, 200_000), (1000, 100_000)],
        })

        _aion_delete_after_resume(
            ch_cache, ["BTCUSDT"], resume_tids, _thresholds_fn
        )

        delete_calls = [
            c for c in ch_cache.client.command.call_args_list
            if "DELETE" in str(c)
        ]
        assert len(delete_calls) == 2, f"Expected 2 DELETEs, got {len(delete_calls)}"

        # Verify the correct partitions were targeted
        delete_sql = [str(c) for c in delete_calls]
        assert any("250" in s for s in delete_sql), "Missing DELETE for threshold 250"
        assert any("750" in s for s in delete_sql), "Missing DELETE for threshold 750"


class TestAllPartitionsCleanSkipsAll:
    """Test 3: All-clean partitions produce zero DELETEs."""

    def test_all_partitions_clean_skips_all(self) -> None:
        """When all partitions are clean, zero DELETE commands and fast elapsed."""
        from opendeviationbar.sidecar import _aion_delete_after_resume

        resume_tids = {"BTCUSDT": 100_000, "ETHUSDT": 200_000}
        ch_cache = _make_ch_cache({
            "BTCUSDT": [(250, 90_000), (500, 95_000), (750, 99_000), (1000, 100_000)],
            "ETHUSDT": [(250, 180_000), (500, 190_000), (750, 195_000), (1000, 200_000)],
        })

        result = _aion_delete_after_resume(
            ch_cache, ["BTCUSDT", "ETHUSDT"], resume_tids, _thresholds_fn
        )

        delete_calls = [
            c for c in ch_cache.client.command.call_args_list
            if "DELETE" in str(c)
        ]
        assert len(delete_calls) == 0


class TestLoggingIncludesSkipCount:
    """Test 4: Log message includes deleted and skipped counts."""

    def test_logging_includes_skip_count(self, caplog: pytest.LogCaptureFixture) -> None:
        """Log output shows deleted/total/skipped breakdown."""
        from opendeviationbar.sidecar import _aion_delete_after_resume

        resume_tids = {"BTCUSDT": 100_000}
        # 1 partition dirty (250), 3 clean
        ch_cache = _make_ch_cache({
            "BTCUSDT": [(250, 100_001), (500, 99_000), (750, 95_000), (1000, 100_000)],
        })

        with caplog.at_level(logging.INFO, logger="opendeviationbar.sidecar"):
            _aion_delete_after_resume(
                ch_cache, ["BTCUSDT"], resume_tids, _thresholds_fn
            )

        # Find the DELETE log line
        delete_logs = [r for r in caplog.records if "DELETE" in r.message and "skipped" in r.message]
        assert len(delete_logs) >= 1, f"Expected DELETE log with skip count, got: {[r.message for r in caplog.records]}"

        msg = delete_logs[0].message
        assert "1/" in msg, f"Expected '1/' (1 deleted) in: {msg}"
        assert "3 skipped" in msg, f"Expected '3 skipped' in: {msg}"


class TestResumeTidNoneSkipsSymbol:
    """Test 5: Symbols with resume_tid=None skip entirely."""

    def test_resume_tid_none_skips_symbol(self) -> None:
        """Symbols with None resume_tid issue zero queries and zero DELETEs."""
        from opendeviationbar.sidecar import _aion_delete_after_resume

        resume_tids = {"BTCUSDT": None, "ETHUSDT": 200_000}
        ch_cache = _make_ch_cache({
            # BTCUSDT should never be queried
            "ETHUSDT": [(250, 190_000), (500, 195_000), (750, 198_000), (1000, 200_000)],
        })

        _aion_delete_after_resume(
            ch_cache, ["BTCUSDT", "ETHUSDT"], resume_tids, _thresholds_fn
        )

        # Verify BTCUSDT was never queried
        query_calls = ch_cache.client.query.call_args_list
        btc_queries = [c for c in query_calls if "BTCUSDT" in str(c)]
        assert len(btc_queries) == 0, f"BTCUSDT should not be queried, got: {btc_queries}"

        # Zero DELETEs (all ETHUSDT partitions are clean)
        delete_calls = [
            c for c in ch_cache.client.command.call_args_list
            if "DELETE" in str(c)
        ]
        assert len(delete_calls) == 0
