"""MON-02: Pre-write gate Aion-awareness tests.

Verifies that the day-mode pre-write gate in bulk_operations.py correctly:
- Passes through Aion symbol cross-midnight bars unfiltered
- Filters out Day-mode symbol cross-midnight bars
- Handles mixed Aion/Day DataFrames correctly
- Falls back to global mode check when symbol column is missing
"""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pandas as pd

# Day boundary in microseconds (86400 * 1_000_000)
DAY_US = 86_400_000_000

# Two timestamps: one in day N, one in day N+1 (cross-midnight)
# 2024-01-15 23:59:00 UTC in microseconds
TS_DAY_N = 1_705_363_140_000_000
# 2024-01-16 00:01:00 UTC in microseconds
TS_DAY_N_PLUS_1 = 1_705_363_260_000_000

# Same-day timestamps (no cross-midnight)
TS_SAME_DAY_OPEN = 1_705_300_000_000_000  # 2024-01-15 ~06:26 UTC
TS_SAME_DAY_CLOSE = 1_705_350_000_000_000  # 2024-01-15 ~20:20 UTC

_BULK_OPS = "opendeviationbar.clickhouse.bulk_operations"


def _make_symbol_entry(symbol: str, mode: str = "day"):
    """Create a mock SymbolEntry with given ouroboros_mode."""
    from opendeviationbar.ouroboros import OuroborosMode

    entry = MagicMock()
    entry.ouroboros_mode = OuroborosMode(mode)
    entry.symbol = symbol
    entry.enabled = True
    return entry


def _make_df_pandas(rows: list[dict]) -> pd.DataFrame:
    """Build a pandas DataFrame mimicking bar data with required columns."""
    df = pd.DataFrame(rows)
    for col in ("open_time_us", "close_time_us"):
        if col in df.columns:
            df[col] = df[col].astype("int64")
    return df


# ---------------------------------------------------------------------------
# Pandas path tests
# ---------------------------------------------------------------------------


class TestPandasPreWriteGate:
    """Test _filter_cross_midnight_pandas."""

    @patch(f"{_BULK_OPS}._get_day_symbols", return_value={"EURUSD"})
    def test_aion_cross_midnight_passes_through(self, mock_day_syms):
        """Aion symbol cross-midnight bars should NOT be filtered."""
        df = _make_df_pandas(
            [
                {
                    "symbol": "BTCUSDT",
                    "open_time_us": TS_DAY_N,
                    "close_time_us": TS_DAY_N_PLUS_1,
                },
            ]
        )

        from opendeviationbar.clickhouse.bulk_operations import (
            _filter_cross_midnight_pandas,
        )

        result = _filter_cross_midnight_pandas(df)
        assert len(result) == 1, "Aion cross-midnight bar should pass through"

    @patch(f"{_BULK_OPS}._get_day_symbols", return_value={"EURUSD"})
    def test_day_cross_midnight_filtered(self, mock_day_syms):
        """Day-mode symbol cross-midnight bars should be filtered."""
        df = _make_df_pandas(
            [
                {
                    "symbol": "EURUSD",
                    "open_time_us": TS_DAY_N,
                    "close_time_us": TS_DAY_N_PLUS_1,
                },
            ]
        )

        from opendeviationbar.clickhouse.bulk_operations import (
            _filter_cross_midnight_pandas,
        )

        result = _filter_cross_midnight_pandas(df)
        assert len(result) == 0, "Day-mode cross-midnight bar should be filtered"

    @patch(f"{_BULK_OPS}._get_day_symbols", return_value={"EURUSD"})
    def test_mixed_df_filters_only_day_mode(self, mock_day_syms):
        """Mixed DataFrame: only Day-mode cross-midnight bars filtered."""
        df = _make_df_pandas(
            [
                {
                    "symbol": "BTCUSDT",
                    "open_time_us": TS_DAY_N,
                    "close_time_us": TS_DAY_N_PLUS_1,
                },
                {
                    "symbol": "EURUSD",
                    "open_time_us": TS_DAY_N,
                    "close_time_us": TS_DAY_N_PLUS_1,
                },
                {
                    "symbol": "BTCUSDT",
                    "open_time_us": TS_SAME_DAY_OPEN,
                    "close_time_us": TS_SAME_DAY_CLOSE,
                },
            ]
        )

        from opendeviationbar.clickhouse.bulk_operations import (
            _filter_cross_midnight_pandas,
        )

        result = _filter_cross_midnight_pandas(df)
        # Should keep: BTCUSDT cross-midnight (aion) + BTCUSDT same-day
        # Should filter: EURUSD cross-midnight (day)
        assert len(result) == 2
        assert list(result["symbol"]) == ["BTCUSDT", "BTCUSDT"]


# ---------------------------------------------------------------------------
# Polars path tests
# ---------------------------------------------------------------------------


class TestPolarsPreWriteGate:
    """Test _filter_cross_midnight_polars."""

    @patch(f"{_BULK_OPS}._get_day_symbols", return_value={"EURUSD"})
    def test_aion_cross_midnight_passes_through(self, mock_day_syms):
        """Aion symbol cross-midnight bars should NOT be filtered (Polars)."""
        import polars as pl

        df = pl.DataFrame(
            {
                "symbol": ["BTCUSDT"],
                "open_time_us": [TS_DAY_N],
                "close_time_us": [TS_DAY_N_PLUS_1],
            }
        )

        from opendeviationbar.clickhouse.bulk_operations import (
            _filter_cross_midnight_polars,
        )

        result = _filter_cross_midnight_polars(df)
        assert result.height == 1, "Aion cross-midnight bar should pass through"

    @patch(f"{_BULK_OPS}._get_day_symbols", return_value={"EURUSD"})
    def test_day_cross_midnight_filtered(self, mock_day_syms):
        """Day-mode symbol cross-midnight bars should be filtered (Polars)."""
        import polars as pl

        df = pl.DataFrame(
            {
                "symbol": ["EURUSD"],
                "open_time_us": [TS_DAY_N],
                "close_time_us": [TS_DAY_N_PLUS_1],
            }
        )

        from opendeviationbar.clickhouse.bulk_operations import (
            _filter_cross_midnight_polars,
        )

        result = _filter_cross_midnight_polars(df)
        assert result.height == 0, "Day-mode cross-midnight bar should be filtered"


# ---------------------------------------------------------------------------
# Fallback tests (no symbol column)
# ---------------------------------------------------------------------------


class TestFallbackGlobalMode:
    """Test backward compatibility when symbol column is missing."""

    @patch(
        "opendeviationbar.ouroboros.get_operational_ouroboros_mode",
        return_value="day",
    )
    def test_no_symbol_column_uses_global_day_mode(self, mock_global_mode):
        """When symbol column missing + global mode=day, cross-midnight filtered."""
        df = _make_df_pandas(
            [
                {
                    "open_time_us": TS_DAY_N,
                    "close_time_us": TS_DAY_N_PLUS_1,
                },
            ]
        )

        from opendeviationbar.clickhouse.bulk_operations import (
            _filter_cross_midnight_pandas,
        )

        result = _filter_cross_midnight_pandas(df)
        assert len(result) == 0, "Global day-mode should filter cross-midnight"

    @patch(
        "opendeviationbar.ouroboros.get_operational_ouroboros_mode",
        return_value="aion",
    )
    def test_no_symbol_column_uses_global_aion_mode(self, mock_global_mode):
        """When symbol column missing + global mode=aion, cross-midnight passes through."""
        df = _make_df_pandas(
            [
                {
                    "open_time_us": TS_DAY_N,
                    "close_time_us": TS_DAY_N_PLUS_1,
                },
            ]
        )

        from opendeviationbar.clickhouse.bulk_operations import (
            _filter_cross_midnight_pandas,
        )

        result = _filter_cross_midnight_pandas(df)
        assert len(result) == 1, "Global aion-mode should pass cross-midnight through"
