"""Tests for midnight preflight verification functions (PREFLIGHT-01, PREFLIGHT-02, PREFLIGHT-03).

Tests _get_midnight_crossover_tid(), _verify_yesterday_orphan(), and
_run_midnight_preflight() from sidecar.py.
All external calls (Binance REST, ClickHouse) are mocked.
"""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest


# ---------------------------------------------------------------------------
# _get_midnight_crossover_tid tests
# ---------------------------------------------------------------------------


def test_get_midnight_crossover_tid_success():
    """Mock fetch_aggtrades_rest returns one trade -- verify agg_trade_id returned."""
    trade = {
        "timestamp": 1700000000000,
        "price": 42000.0,
        "quantity": 0.5,
        "agg_trade_id": 999001,
        "first_trade_id": 999001,
        "last_trade_id": 999001,
        "is_buyer_maker": False,
    }
    with patch(
        "opendeviationbar._core.fetch_aggtrades_rest", return_value=[trade]
    ):
        from opendeviationbar.sidecar import _get_midnight_crossover_tid

        result = _get_midnight_crossover_tid("BTCUSDT")

    assert result == 999001


def test_get_midnight_crossover_tid_empty():
    """Mock returns empty list -- verify returns None."""
    with patch("opendeviationbar._core.fetch_aggtrades_rest", return_value=[]):
        from opendeviationbar.sidecar import _get_midnight_crossover_tid

        result = _get_midnight_crossover_tid("BTCUSDT")

    assert result is None


def test_get_midnight_crossover_tid_network_error():
    """Mock raises RuntimeError -- verify returns None (no crash)."""
    with patch(
        "opendeviationbar._core.fetch_aggtrades_rest",
        side_effect=RuntimeError("connection refused"),
    ):
        from opendeviationbar.sidecar import _get_midnight_crossover_tid

        result = _get_midnight_crossover_tid("BTCUSDT")

    assert result is None


# ---------------------------------------------------------------------------
# _verify_yesterday_orphan tests
# ---------------------------------------------------------------------------


def test_verify_yesterday_orphan_match():
    """Orphan tid == crossover_tid - 1 -- verify returns True."""
    mock_cache = MagicMock()
    mock_cache.get_yesterday_orphan_tid.return_value = 999000
    mock_cache.__enter__ = MagicMock(return_value=mock_cache)
    mock_cache.__exit__ = MagicMock(return_value=False)

    with patch(
        "opendeviationbar.clickhouse.cache.OpenDeviationBarCache",
        return_value=mock_cache,
    ):
        from opendeviationbar.sidecar import _verify_yesterday_orphan

        result = _verify_yesterday_orphan("BTCUSDT", crossover_tid=999001)

    assert result is True
    mock_cache.get_yesterday_orphan_tid.assert_called_once_with("BTCUSDT")


def test_verify_yesterday_orphan_mismatch():
    """Orphan tid != crossover_tid - 1 -- verify returns False."""
    mock_cache = MagicMock()
    mock_cache.get_yesterday_orphan_tid.return_value = 998000  # wrong
    mock_cache.__enter__ = MagicMock(return_value=mock_cache)
    mock_cache.__exit__ = MagicMock(return_value=False)

    with patch(
        "opendeviationbar.clickhouse.cache.OpenDeviationBarCache",
        return_value=mock_cache,
    ):
        from opendeviationbar.sidecar import _verify_yesterday_orphan

        result = _verify_yesterday_orphan("BTCUSDT", crossover_tid=999001)

    assert result is False


def test_verify_yesterday_orphan_no_orphan():
    """No orphan bar found (returns None) -- verify returns False."""
    mock_cache = MagicMock()
    mock_cache.get_yesterday_orphan_tid.return_value = None
    mock_cache.__enter__ = MagicMock(return_value=mock_cache)
    mock_cache.__exit__ = MagicMock(return_value=False)

    with patch(
        "opendeviationbar.clickhouse.cache.OpenDeviationBarCache",
        return_value=mock_cache,
    ):
        from opendeviationbar.sidecar import _verify_yesterday_orphan

        result = _verify_yesterday_orphan("BTCUSDT", crossover_tid=999001)

    assert result is False


# ---------------------------------------------------------------------------
# _run_midnight_preflight integration tests (PREFLIGHT-03)
# ---------------------------------------------------------------------------


def test_run_midnight_preflight_overrides_seeds():
    """Crossover TID found for one symbol -- verify seed override for all thresholds."""
    mock_engine = MagicMock()
    symbols = ["BTCUSDT"]
    thresholds = [250, 500, 750]

    with (
        patch(
            "opendeviationbar.sidecar._get_midnight_crossover_tid",
            return_value=999001,
        ),
        patch(
            "opendeviationbar.sidecar._verify_yesterday_orphan",
            return_value=True,
        ),
    ):
        from opendeviationbar.sidecar import _run_midnight_preflight

        result = _run_midnight_preflight(mock_engine, symbols, thresholds)

    assert result == {"BTCUSDT": 999001}
    # seed_trade_id called once per threshold with crossover_tid - 1
    assert mock_engine.seed_trade_id.call_count == 3
    mock_engine.seed_trade_id.assert_any_call("BTCUSDT", 250, 999000)
    mock_engine.seed_trade_id.assert_any_call("BTCUSDT", 500, 999000)
    mock_engine.seed_trade_id.assert_any_call("BTCUSDT", 750, 999000)


def test_run_midnight_preflight_partial_failure():
    """Crossover TID found for A but not B -- only A gets seed override."""
    mock_engine = MagicMock()
    symbols = ["BTCUSDT", "ETHUSDT"]
    thresholds = [250]

    def _mock_crossover(symbol):
        return 999001 if symbol == "BTCUSDT" else None

    with (
        patch(
            "opendeviationbar.sidecar._get_midnight_crossover_tid",
            side_effect=_mock_crossover,
        ),
        patch(
            "opendeviationbar.sidecar._verify_yesterday_orphan",
            return_value=True,
        ),
    ):
        from opendeviationbar.sidecar import _run_midnight_preflight

        result = _run_midnight_preflight(mock_engine, symbols, thresholds)

    assert result == {"BTCUSDT": 999001}
    assert "ETHUSDT" not in result
    mock_engine.seed_trade_id.assert_called_once_with("BTCUSDT", 250, 999000)


def test_run_midnight_preflight_all_fail():
    """All crossover queries fail -- no seed overrides, empty dict returned."""
    mock_engine = MagicMock()
    symbols = ["BTCUSDT", "ETHUSDT"]
    thresholds = [250, 500]

    with patch(
        "opendeviationbar.sidecar._get_midnight_crossover_tid",
        return_value=None,
    ):
        from opendeviationbar.sidecar import _run_midnight_preflight

        result = _run_midnight_preflight(mock_engine, symbols, thresholds)

    assert result == {}
    mock_engine.seed_trade_id.assert_not_called()


def test_run_midnight_preflight_verification_mismatch_still_overrides():
    """Orphan mismatch does NOT prevent seed override -- verification is log-only."""
    mock_engine = MagicMock()
    symbols = ["BTCUSDT"]
    thresholds = [250]

    with (
        patch(
            "opendeviationbar.sidecar._get_midnight_crossover_tid",
            return_value=888001,
        ),
        patch(
            "opendeviationbar.sidecar._verify_yesterday_orphan",
            return_value=False,  # mismatch!
        ),
    ):
        from opendeviationbar.sidecar import _run_midnight_preflight

        result = _run_midnight_preflight(mock_engine, symbols, thresholds)

    # Despite verification failure, seed IS overridden
    assert result == {"BTCUSDT": 888001}
    mock_engine.seed_trade_id.assert_called_once_with("BTCUSDT", 250, 888000)
