from __future__ import annotations

import datetime as dt
from zoneinfo import ZoneInfo

import pytest

from libs.backtest.artifacts import (
    _resolve_trade_sleeve,
    create_run_directory,
    write_non_core_allocator_shadow_report,
)
from libs.backtest.domain import BacktestConfig, Candidate, ExitReason, FilledTrade
from libs.backtest.non_core_allocator import (
    classify_non_core_overlap_class,
    classify_parking_class,
    compute_marginal_score,
    compute_native_rank_pct,
    compute_overlap_penalty,
    normalize_hold_days_est,
    normalize_liquidity_penalty,
    normalize_parking_proxy,
)

_UTC = ZoneInfo("UTC")
_NOW = dt.datetime(2026, 1, 5, 21, 0, tzinfo=_UTC)
_TODAY = dt.date(2026, 1, 5)
_TOMORROW = dt.date(2026, 1, 6)


def _make_candidate(**kwargs) -> Candidate:
    defaults = dict(
        event_id="EVT::TEST",
        symbol="AAPL",
        score=0.8,
        sector="Technology",
        event_type="earnings",
        event_timestamp=_NOW,
        filing_time_bucket="post_market",
        reaction_date=_TODAY,
        execution_date=_TOMORROW,
        entry_price_est=100.0,
        avg_dollar_volume=5_000_000.0,
        score_bucket="high",
        features={},
    )
    defaults.update(kwargs)
    return Candidate(**defaults)


def _make_trade(**kwargs) -> FilledTrade:
    defaults = dict(
        trade_id="t1",
        position_id="p1",
        event_id="EVT::TEST",
        symbol="AAPL",
        entry_date=_TODAY,
        exit_date=_TOMORROW,
        entry_price=100.0,
        exit_price=101.0,
        exit_reason=ExitReason.TARGET,
        shares=10,
        commission=0.1,
        slippage_bps=10.0,
        gross_pnl=10.0,
        net_pnl=9.9,
        pnl_pct=0.01,
        r_multiple=1.0,
        holding_days=1,
    )
    defaults.update(kwargs)
    return FilledTrade(**defaults)


def test_non_core_allocator_config_defaults():
    cfg = BacktestConfig(strategy_name="test", dataset_snapshot_id="snap_001")
    assert cfg.non_core_allocator_v2.enabled is False
    assert cfg.non_core_allocator_v2.mode == "shadow"
    assert cfg.non_core_allocator_v2.scope == "non_core"
    assert cfg.non_core_allocator_v2.benchmark_mode == "current_effective_parking"
    assert cfg.non_core_allocator_v2.weights.parking_opportunity_penalty == pytest.approx(0.35)


def test_trade_sleeve_resolves_form4_and_risk_off():
    form4_trade = _make_trade(trade_id="f4")
    risk_trade = _make_trade(trade_id="ro")
    candidate_map = {
        "f4": _make_candidate(features={"trade_sleeve": "form4"}),
        "ro": _make_candidate(features={"trade_sleeve": "risk_off_alpha"}),
    }
    assert _resolve_trade_sleeve(form4_trade, candidate_map) == "form4"
    assert _resolve_trade_sleeve(risk_trade, candidate_map) == "risk_off_alpha"


def test_parking_and_overlap_classification():
    assert classify_parking_class("SGOV") == "cash"
    assert classify_parking_class("GLD") == "gold"
    assert classify_parking_class("QQQM") == "equity_beta"
    assert classify_non_core_overlap_class("idle_alpha", engine_id="idle_macro_breadth_smh_postalloc") == "equity_beta_high"
    assert classify_non_core_overlap_class("form4", symbol="AAPL") == "equity_beta_med"
    assert classify_non_core_overlap_class("risk_off_alpha", symbol="GLD") == "gold"


def test_rank_hold_liquidity_and_parking_proxy_normalization():
    assert compute_native_rank_pct(0, 1) == pytest.approx(1.0)
    assert compute_native_rank_pct(1, 3) == pytest.approx(0.5)
    assert normalize_hold_days_est(45) == pytest.approx(1.0)
    assert normalize_hold_days_est(15) == pytest.approx(0.5)
    assert normalize_liquidity_penalty(50_000, 5_000_000) == pytest.approx(0.5)
    assert normalize_parking_proxy("sgov", parking_momentum_20=0.0, hold_days_est=20, sgov_annual_rate=0.05) == pytest.approx((0.05 * 20 / 252) / 0.02)
    assert normalize_parking_proxy("gld", parking_momentum_20=0.12, hold_days_est=20, sgov_annual_rate=0.05) == pytest.approx(1.0)


def test_overlap_penalty_matrix():
    assert compute_overlap_penalty("gold", "gold") == pytest.approx(1.0)
    assert compute_overlap_penalty("equity_beta_high", "equity_beta") == pytest.approx(1.0)
    assert compute_overlap_penalty("equity_beta_med", "equity_beta") == pytest.approx(0.6)
    assert compute_overlap_penalty("equity_beta_med", "cash") == pytest.approx(0.0)


def test_marginal_score_formula_matches_plan():
    score = compute_marginal_score(
        native_rank_pct=1.0,
        hold_norm=0.5,
        liquidity_penalty_norm=0.25,
        overlap_penalty_norm=0.6,
        parking_proxy_norm=0.4,
    )
    expected = 1.0 - 0.20 * 0.5 - 0.25 * 0.25 - 0.20 * 0.6 - 0.35 * 0.4
    assert score == pytest.approx(expected)


def test_shadow_report_writer_outputs_human_readable_report(tmp_path):
    run_dir = create_run_directory(tmp_path, "allocator-shadow-test")
    rows = [
        {
            "date": "2026-01-05",
            "engine_id": "idle_macro_breadth_smh_postalloc",
            "event_id": "EVT::IDLE",
            "symbol": "SMH",
            "allocator_v2_family": "idle_alpha",
            "allocator_v2_marginal_score": 0.91,
            "allocator_v2_requested_cash_est": 125000.0,
            "allocator_v2_parking_symbol": "sgov",
            "allocator_v2_shadow_selected": True,
            "allocator_v2_live_selected": True,
            "allocator_v2_live_vs_shadow_disagree": False,
            "allocator_v2_complete": True,
            "allocator_v2_shadow_decision": "selected_shadow_v2",
        },
        {
            "date": "2026-01-06",
            "engine_id": "idle_form4_capture",
            "event_id": "EVT::F4",
            "symbol": "MSFT",
            "allocator_v2_family": "form4",
            "allocator_v2_marginal_score": 0.72,
            "allocator_v2_requested_cash_est": 80000.0,
            "allocator_v2_parking_symbol": "qqqm",
            "allocator_v2_shadow_selected": True,
            "allocator_v2_live_selected": False,
            "allocator_v2_live_vs_shadow_disagree": True,
            "allocator_v2_complete": True,
            "allocator_v2_shadow_decision": "selected_shadow_v2",
        },
        {
            "date": "2026-01-06",
            "engine_id": "idle_form4_capture",
            "event_id": "EVT::F4B",
            "symbol": "NVDA",
            "allocator_v2_family": "form4",
            "allocator_v2_marginal_score": 0.55,
            "allocator_v2_requested_cash_est": 65000.0,
            "allocator_v2_parking_symbol": "qqqm",
            "allocator_v2_shadow_selected": False,
            "allocator_v2_live_selected": False,
            "allocator_v2_live_vs_shadow_disagree": False,
            "allocator_v2_complete": True,
            "allocator_v2_shadow_decision": "blocked_by_better_opportunity",
        },
    ]
    report_path = write_non_core_allocator_shadow_report(run_dir, rows)
    assert report_path is not None
    text = report_path.read_text()
    assert "# Non-Core Allocator Shadow Report" in text
    assert "## Sleeve Summary" in text
    assert "## Top Shadow-Selected But Live-Skipped" in text
    assert "## Top Better-Opportunity Blocks" in text
    assert "`MSFT`" in text
