"""Unit tests for checksum verification integration in tick_cache_seeder.

Offline tests -- no network calls. Validates SEEDER-01 (SHA-256 verification),
SEEDER-02 (retry + LOUD alert), and SEEDER-03 (registry persistence).
"""

from __future__ import annotations

import sys
import threading
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

# Add scripts dir to path so we can import tick_cache_seeder
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

# Known SHA-256 hash of b"fake zip data" -- computed once via hashlib
_FAKE_ZIP = b"fake zip data"
_FAKE_HASH: str | None = None  # set by fixture


@pytest.fixture(autouse=True)
def _compute_fake_hash():
    """Compute the real SHA-256 of _FAKE_ZIP using hashlib (no Rust needed)."""
    import hashlib

    global _FAKE_HASH  # noqa: PLW0603
    _FAKE_HASH = hashlib.sha256(_FAKE_ZIP).hexdigest()


def _checksum_file_content(hex_hash: str, filename: str = "SYM-aggTrades-2024-01-15.zip") -> str:
    """Build a Vision CHECKSUM file body."""
    return f"{hex_hash}  {filename}\n"


# ---------------------------------------------------------------------------
# Test 1-4: _verify_vision_checksum
# ---------------------------------------------------------------------------


class TestVerifyVisionChecksum:
    """Tests for _verify_vision_checksum() helper."""

    @patch("tick_cache_seeder.verify_checksum_hex")
    @patch("tick_cache_seeder.parse_checksum_file_content")
    @patch("tick_cache_seeder.requests.get")
    def test_verified_true_on_match(self, mock_get, mock_parse, mock_verify):
        """Test 1: Returns verified=True + sha256 when ZIP matches CHECKSUM."""
        from tick_cache_seeder import _verify_vision_checksum

        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.text = _checksum_file_content(_FAKE_HASH)
        mock_get.return_value = mock_resp

        mock_parse.return_value = _FAKE_HASH
        mock_verify.return_value = {
            "verified": True,
            "expected": _FAKE_HASH,
            "actual": _FAKE_HASH,
            "skipped": False,
        }

        result = _verify_vision_checksum(_FAKE_ZIP, "BTCUSDT", "2024-01-15", timeout_s=10)
        assert result["verified"] is True
        assert result["sha256"] == _FAKE_HASH

    @patch("tick_cache_seeder.requests.get")
    def test_soft_skip_on_404(self, mock_get):
        """Test 2: Returns soft-skip dict when CHECKSUM URL returns 404."""
        from tick_cache_seeder import _verify_vision_checksum

        mock_resp = MagicMock()
        mock_resp.status_code = 404
        mock_get.return_value = mock_resp

        result = _verify_vision_checksum(_FAKE_ZIP, "BTCUSDT", "2024-01-15", timeout_s=10)
        assert result["verified"] is False
        assert result["sha256"] is None
        assert result["reason"] == "404"

    @patch("tick_cache_seeder.requests.get")
    def test_soft_skip_on_timeout(self, mock_get):
        """Test 3: Returns soft-skip dict on CHECKSUM fetch timeout."""
        import requests as req_lib
        from tick_cache_seeder import _verify_vision_checksum

        mock_get.side_effect = req_lib.Timeout("timed out")

        result = _verify_vision_checksum(_FAKE_ZIP, "BTCUSDT", "2024-01-15", timeout_s=10)
        assert result["verified"] is False
        assert result["sha256"] is None
        assert result["reason"] == "timeout"

    @patch("tick_cache_seeder.verify_checksum_hex")
    @patch("tick_cache_seeder.parse_checksum_file_content")
    @patch("tick_cache_seeder.requests.get")
    def test_raises_on_mismatch(self, mock_get, mock_parse, mock_verify):
        """Test 4: Raises RuntimeError on hash mismatch."""
        from tick_cache_seeder import _verify_vision_checksum

        mock_resp = MagicMock()
        mock_resp.status_code = 200
        mock_resp.text = _checksum_file_content("a" * 64)
        mock_get.return_value = mock_resp

        mock_parse.return_value = "a" * 64
        mock_verify.side_effect = RuntimeError("Checksum mismatch: expected aaa..., got bbb...")

        with pytest.raises(RuntimeError, match="mismatch"):
            _verify_vision_checksum(_FAKE_ZIP, "BTCUSDT", "2024-01-15", timeout_s=10)


# ---------------------------------------------------------------------------
# Test 5-7: _download_and_verify_vision_day
# ---------------------------------------------------------------------------


class TestDownloadAndVerifyVisionDay:
    """Tests for _download_and_verify_vision_day() wrapper."""

    @patch("tick_cache_seeder.send_telegram")
    @patch("tick_cache_seeder._verify_vision_checksum")
    @patch("tick_cache_seeder._download_vision_day_raw")
    def test_retries_on_mismatch(self, mock_raw, mock_verify, mock_tg):
        """Test 5: Retries download up to 2 times on checksum mismatch."""
        import polars as pl
        from tick_cache_seeder import _download_and_verify_vision_day

        mock_df = pl.DataFrame({"agg_trade_id": [1]})
        mock_raw.return_value = (_FAKE_ZIP, mock_df)
        mock_verify.side_effect = RuntimeError("Checksum mismatch")

        registry = MagicMock()
        stats = {"pass": 0, "fail": 0, "skip": 0}
        lock = threading.Lock()

        result = _download_and_verify_vision_day(
            "BTCUSDT", "2024-01-15", timeout_s=10, registry=registry,
            stats=stats, stats_lock=lock, max_retries=2,
        )

        assert result is None  # all retries exhausted
        # 1 initial + 2 retries = 3 total calls
        assert mock_raw.call_count == 3

    @patch("tick_cache_seeder._verify_vision_checksum")
    @patch("tick_cache_seeder._download_vision_day_raw")
    def test_records_in_registry_on_success(self, mock_raw, mock_verify):
        """Test 6: Records successful verification in ChecksumRegistry."""
        import polars as pl
        from tick_cache_seeder import _download_and_verify_vision_day

        mock_df = pl.DataFrame({"agg_trade_id": [1]})
        mock_raw.return_value = (_FAKE_ZIP, mock_df)
        mock_verify.return_value = {"verified": True, "sha256": _FAKE_HASH}

        registry = MagicMock()
        stats = {"pass": 0, "fail": 0, "skip": 0}
        lock = threading.Lock()

        result = _download_and_verify_vision_day(
            "BTCUSDT", "2024-01-15", timeout_s=10, registry=registry,
            stats=stats, stats_lock=lock,
        )

        assert result is not None
        registry.add.assert_called_once_with(
            symbol="BTCUSDT", date="2024-01-15", sha256=_FAKE_HASH, source="seeder",
        )
        assert stats["pass"] == 1

    @patch("tick_cache_seeder.send_telegram")
    @patch("tick_cache_seeder._verify_vision_checksum")
    @patch("tick_cache_seeder._download_vision_day_raw")
    def test_loud_telegram_on_persistent_failure(self, mock_raw, mock_verify, mock_tg):
        """Test 7: Sends LOUD Telegram alert when all retries exhausted."""
        import polars as pl
        from tick_cache_seeder import _download_and_verify_vision_day

        mock_df = pl.DataFrame({"agg_trade_id": [1]})
        mock_raw.return_value = (_FAKE_ZIP, mock_df)
        mock_verify.side_effect = RuntimeError("Checksum mismatch: expected aaa, got bbb")

        registry = MagicMock()
        stats = {"pass": 0, "fail": 0, "skip": 0}
        lock = threading.Lock()

        _download_and_verify_vision_day(
            "BTCUSDT", "2024-01-15", timeout_s=10, registry=registry,
            stats=stats, stats_lock=lock, max_retries=2,
        )

        mock_tg.assert_called_once()
        call_args = mock_tg.call_args
        msg = call_args[0][0] if call_args[0] else call_args[1].get("message", "")
        assert "CHECKSUM FAIL" in msg
        assert "BTCUSDT" in msg
        assert stats["fail"] == 1


# ---------------------------------------------------------------------------
# Test 8: Stats accumulation
# ---------------------------------------------------------------------------


class TestChecksumStats:
    """Tests for checksum stats counters."""

    @patch("tick_cache_seeder._verify_vision_checksum")
    @patch("tick_cache_seeder._download_vision_day_raw")
    def test_stats_accumulate(self, mock_raw, mock_verify):
        """Test 8: pass/fail/skip counters accumulate correctly."""
        import polars as pl
        from tick_cache_seeder import _download_and_verify_vision_day

        mock_df = pl.DataFrame({"agg_trade_id": [1]})
        mock_raw.return_value = (_FAKE_ZIP, mock_df)

        registry = MagicMock()
        stats = {"pass": 0, "fail": 0, "skip": 0}
        lock = threading.Lock()

        # Call 1: verified=True (pass)
        mock_verify.return_value = {"verified": True, "sha256": _FAKE_HASH}
        _download_and_verify_vision_day(
            "BTCUSDT", "2024-01-15", timeout_s=10, registry=registry,
            stats=stats, stats_lock=lock,
        )

        # Call 2: soft-skip (404)
        mock_verify.return_value = {"verified": False, "sha256": None, "reason": "404"}
        _download_and_verify_vision_day(
            "BTCUSDT", "2024-01-16", timeout_s=10, registry=registry,
            stats=stats, stats_lock=lock,
        )

        # Call 3: another pass
        mock_verify.return_value = {"verified": True, "sha256": _FAKE_HASH}
        _download_and_verify_vision_day(
            "BTCUSDT", "2024-01-17", timeout_s=10, registry=registry,
            stats=stats, stats_lock=lock,
        )

        assert stats["pass"] == 2
        assert stats["skip"] == 1
        assert stats["fail"] == 0

    @patch("tick_cache_seeder.send_telegram")
    @patch("tick_cache_seeder._verify_vision_checksum")
    @patch("tick_cache_seeder._download_vision_day_raw")
    def test_fail_counter_on_persistent_mismatch(self, mock_raw, mock_verify, mock_tg):
        """Test 8b: fail counter increments on persistent mismatch."""
        import polars as pl
        from tick_cache_seeder import _download_and_verify_vision_day

        mock_df = pl.DataFrame({"agg_trade_id": [1]})
        mock_raw.return_value = (_FAKE_ZIP, mock_df)
        mock_verify.side_effect = RuntimeError("Checksum mismatch")

        registry = MagicMock()
        stats = {"pass": 0, "fail": 0, "skip": 0}
        lock = threading.Lock()

        _download_and_verify_vision_day(
            "BTCUSDT", "2024-01-15", timeout_s=10, registry=registry,
            stats=stats, stats_lock=lock, max_retries=2,
        )

        assert stats["fail"] == 1
        assert stats["pass"] == 0
