from datetime import date, timedelta

from scripts.import_dolthub_earnings import _align_symbol
from scripts.run_earnings_research import (
    _analyse_2a_trades,
    _build_sue_series,
    _mechanical_sue_grade,
)


def test_dolthub_alignment_is_monotonic_across_close_calendar_events() -> None:
    events = [
        {"announce_date": date(2020, 3, 17), "announce_time": "bmo"},
        {"announce_date": date(2020, 4, 30), "announce_time": "bmo"},
    ]
    periods = [
        {"period_end_date": date(2019, 12, 31)},
        {"period_end_date": date(2020, 3, 31)},
    ]
    matches, unmatched_events, unmatched_periods = _align_symbol(
        events, periods, max_lag_days=90, max_lead_days=14
    )
    assert matches == [(0, 0), (1, 1)]
    assert unmatched_events == []
    assert unmatched_periods == []


def test_dolthub_alignment_allows_fiscal_period_label_after_announcement() -> None:
    events = [
        {"announce_date": date(2023, 2, 28), "announce_time": "bmo"},
        {"announce_date": date(2023, 5, 23), "announce_time": "bmo"},
    ]
    periods = [
        {"period_end_date": date(2023, 2, 28)},
        {"period_end_date": date(2023, 5, 31)},
    ]
    matches, _, _ = _align_symbol(
        events, periods, max_lag_days=90, max_lead_days=14
    )
    assert matches == [(0, 0), (1, 1)]


def test_2a_uses_net_r_strict_hold_and_next_session_stop() -> None:
    calendar = [
        date(2024, 1, 2),
        date(2024, 1, 3),
        date(2024, 1, 4),
        date(2024, 1, 5),
        date(2024, 1, 8),
        date(2024, 1, 9),
    ]
    events = [
        {
            "symbol": "AAPL",
            "announce_date": date(2024, 1, 5),
        }
    ]
    trades = [
        {
            "symbol": "AAPL",
            "entry_date": "2024-01-03",
            "exit_date": "2024-01-08",
            "entry": 100.0,
            "initial_stop": 90.0,
            "fill": 90.0,
            "r": -1.0,
            "reason": "stop",
        },
        {
            "symbol": "MSFT",
            "entry_date": "2024-01-02",
            "exit_date": "2024-01-09",
            "entry": 100.0,
            "initial_stop": 90.0,
            "fill": 110.0,
            "r": 1.0,
            "reason": "time",
        },
    ]
    result = _analyse_2a_trades(
        trades, events, calendar, cost_per_side=0.001
    )
    assert result["q1_loss_concentration"]["losses_count"] == 1
    assert result["q1_loss_concentration"]["losses_with_announcement_count"] == 1
    assert (
        result["q2_entries_within_3_trading_days_before_announcement"][
            "pre_earnings"
        ]["count"]
        == 1
    )
    assert (
        result["q3_stop_exits_within_1_trading_day_after_announcement"][
            "stops_after_earnings"
        ]["count"]
        == 1
    )
    assert result["q1_loss_concentration"]["loss_definition"] == (
        "realized_net_R <= -1.0"
    )


def test_sue_needs_four_prior_surprises_and_starts_next_trading_day() -> None:
    dates = [date(2024, 1, 1) + timedelta(days=index) for index in range(100)]
    columns = (
        [value.toordinal() for value in dates],
        [100.0] * len(dates),
        [101.0] * len(dates),
        [99.0] * len(dates),
        [100.0] * len(dates),
        [1_000_000] * len(dates),
    )
    event_dates = [date(2024, 1, 2) + timedelta(days=10 * index) for index in range(5)]
    surprises = [0.1, -0.2, 0.3, -0.1, 0.4]
    events = {
        "AAPL": [
            {
                "announce_date": event_date,
                "eps_actual": 1.0 + surprise,
                "eps_estimate": 1.0,
            }
            for event_date, surprise in zip(event_dates, surprises)
        ]
    }
    series, counts = _build_sue_series(
        events, {"AAPL": columns}, use_price_fallback=False
    )
    first_live = event_dates[-1] + timedelta(days=1)
    assert first_live in series["AAPL"]
    assert event_dates[-1] not in series["AAPL"]
    assert counts["standard_scaled_events"] == 1
    assert counts["price_fallback_events"] == 0


def test_sue_uses_period_history_only_for_scaling() -> None:
    dates = [date(2020, 1, 1) + timedelta(days=index) for index in range(100)]
    columns = (
        [value.toordinal() for value in dates],
        [100.0] * len(dates),
        [101.0] * len(dates),
        [99.0] * len(dates),
        [100.0] * len(dates),
        [1_000_000] * len(dates),
    )
    event_date = date(2020, 2, 3)
    events = {
        "AAPL": [
            {
                "announce_date": event_date,
                "period_end_date": date(2019, 12, 31),
                "eps_actual": 1.4,
                "eps_estimate": 1.0,
            }
        ]
    }
    history = {
        "AAPL": [
            {
                "period_end_date": date(2018, 12, 31)
                + timedelta(days=90 * index),
                "eps_actual": 1.0 + surprise,
                "eps_estimate": 1.0,
            }
            for index, surprise in enumerate([0.1, -0.2, 0.3, -0.1])
        ]
    }
    series, counts = _build_sue_series(
        events,
        {"AAPL": columns},
        use_price_fallback=False,
        surprise_history_by_symbol=history,
    )
    assert event_date + timedelta(days=1) in series["AAPL"]
    assert event_date not in series["AAPL"]
    assert counts["events_scaled_from_period_history"] == 1


def test_sue_grade_requires_positive_both_eras() -> None:
    full = {"mean_ic": 0.03, "reliable": True}
    passed, stable = _mechanical_sue_grade(
        full, {"mean_ic": 0.01}, {"mean_ic": 0.02}
    )
    assert passed is True
    assert stable is True
    failed, stable = _mechanical_sue_grade(
        full, {"mean_ic": -0.01}, {"mean_ic": 0.02}
    )
    assert failed is False
    assert stable is False
