"""Unit tests for scenario robustness.py — RRS computation logic."""
import pytest

from libs.backtest.domain import MetricsBundle
from libs.backtest.scenarios.robustness import (
    RegimeRobustnessReport,
    ScenarioResult,
    compute_rrs,
)


def _make_result(
    name: str,
    sharpe: float = 1.0,
    total_return: float = 10.0,
    max_dd: float = 10.0,
    win_rate: float = 0.55,
    profit_factor: float = 1.5,
    trade_count: int = 20,
) -> ScenarioResult:
    """Helper: construct a minimal ScenarioResult."""
    m = MetricsBundle(
        trade_count=trade_count,
        win_rate=win_rate,
        avg_win_pct=2.0,
        avg_loss_pct=-1.0,
        profit_factor=profit_factor,
        total_return_pct=total_return,
        max_drawdown_pct=max_dd,
        sharpe_ratio=sharpe,
    )
    return ScenarioResult(
        scenario_name=name,
        metrics=m,
        trade_count=trade_count,
        sharpe_ratio=sharpe,
        total_return_pct=total_return,
        max_drawdown_pct=max_dd,
        win_rate=win_rate,
        profit_factor=profit_factor,
    )


@pytest.mark.unit
class TestComputeRRS:
    def test_empty_results_returns_zeros(self):
        rrs, si, br, dr, rt, st = compute_rrs({})
        assert rrs == 0.0
        assert si == 0.0
        assert br == 0.0

    def test_signal_integrity_perfect_when_no_signal_sharpe_zero(self):
        results = {
            "no_signal": _make_result("no_signal", sharpe=0.0),
            "strong_signal": _make_result("strong_signal", sharpe=1.5),
        }
        _, si, _, _, _, _ = compute_rrs(results)
        assert si == 100.0

    def test_signal_integrity_perfect_when_no_signal_sharpe_negative(self):
        results = {
            "no_signal": _make_result("no_signal", sharpe=-0.5),
            "strong_signal": _make_result("strong_signal", sharpe=1.5),
        }
        _, si, _, _, _, _ = compute_rrs(results)
        assert si == 100.0

    def test_signal_integrity_zero_when_no_signal_equals_strong_signal(self):
        results = {
            "no_signal": _make_result("no_signal", sharpe=1.5),
            "strong_signal": _make_result("strong_signal", sharpe=1.5),
        }
        _, si, _, _, _, _ = compute_rrs(results)
        assert si == pytest.approx(0.0, abs=1.0)

    def test_signal_integrity_neutral_when_no_signal_scenario_missing(self):
        results = {"steady_bull": _make_result("steady_bull", sharpe=1.0)}
        _, si, _, _, _, _ = compute_rrs(results)
        assert si == 50.0

    def test_breadth_all_positive(self):
        results = {
            "a": _make_result("a", sharpe=0.5),
            "b": _make_result("b", sharpe=1.0),
            "c": _make_result("c", sharpe=0.1),
        }
        _, _, br, _, _, _ = compute_rrs(results)
        assert br == pytest.approx(100.0)

    def test_breadth_all_negative(self):
        results = {
            "a": _make_result("a", sharpe=-0.5),
            "b": _make_result("b", sharpe=-1.0),
        }
        _, _, br, _, _, _ = compute_rrs(results)
        assert br == pytest.approx(0.0)

    def test_breadth_half_positive(self):
        results = {
            "a": _make_result("a", sharpe=1.0),
            "b": _make_result("b", sharpe=-1.0),
        }
        _, _, br, _, _, _ = compute_rrs(results)
        assert br == pytest.approx(50.0)

    def test_drawdown_resilience_zero_dd_gives_100(self):
        results = {"a": _make_result("a", max_dd=0.0)}
        _, _, _, dr, _, _ = compute_rrs(results)
        assert dr == pytest.approx(100.0)

    def test_drawdown_resilience_50pct_dd_gives_0(self):
        results = {"a": _make_result("a", max_dd=50.0)}
        _, _, _, dr, _, _ = compute_rrs(results)
        assert dr == pytest.approx(0.0)

    def test_drawdown_resilience_clamped_not_negative(self):
        results = {"a": _make_result("a", max_dd=80.0)}
        _, _, _, dr, _, _ = compute_rrs(results)
        assert dr >= 0.0

    def test_regime_transition_neutral_when_missing(self):
        results = {"steady_bull": _make_result("steady_bull", sharpe=1.0)}
        _, _, _, _, rt, _ = compute_rrs(results)
        assert rt == 50.0

    def test_regime_transition_good_when_above_median(self):
        results = {
            "a": _make_result("a", sharpe=0.5),
            "b": _make_result("b", sharpe=0.5),
            "regime_switch": _make_result("regime_switch", sharpe=2.0),
        }
        _, _, _, _, rt, _ = compute_rrs(results)
        # regime_switch > median → should be > 50
        assert rt > 50.0

    def test_stability_perfect_when_all_same_sharpe(self):
        results = {
            "a": _make_result("a", sharpe=1.0),
            "b": _make_result("b", sharpe=1.0),
            "c": _make_result("c", sharpe=1.0),
        }
        _, _, _, _, _, st = compute_rrs(results)
        assert st == pytest.approx(100.0)

    def test_stability_lower_when_high_variance(self):
        low_var = {
            "a": _make_result("a", sharpe=1.0),
            "b": _make_result("b", sharpe=1.1),
            "c": _make_result("c", sharpe=0.9),
        }
        high_var = {
            "a": _make_result("a", sharpe=3.0),
            "b": _make_result("b", sharpe=-0.5),
            "c": _make_result("c", sharpe=0.1),
        }
        _, _, _, _, _, st_low = compute_rrs(low_var)
        _, _, _, _, _, st_high = compute_rrs(high_var)
        assert st_low > st_high

    def test_rrs_weighted_sum(self):
        """RRS = 0.25*si + 0.25*br + 0.20*dr + 0.15*rt + 0.15*st."""
        results = {
            "no_signal": _make_result("no_signal", sharpe=-0.1),
            "strong_signal": _make_result("strong_signal", sharpe=1.5),
            "steady_bull": _make_result("steady_bull", sharpe=1.2),
            "steady_bear": _make_result("steady_bear", sharpe=0.3),
            "regime_switch": _make_result("regime_switch", sharpe=0.8),
        }
        rrs, si, br, dr, rt, st = compute_rrs(results)
        expected = 0.25 * si + 0.25 * br + 0.20 * dr + 0.15 * rt + 0.15 * st
        assert rrs == pytest.approx(expected, rel=1e-6)

    def test_rrs_bounded_0_to_100(self):
        results = {
            "no_signal": _make_result("no_signal", sharpe=-0.5),
            "strong_signal": _make_result("strong_signal", sharpe=2.0),
            "a": _make_result("a", sharpe=1.0),
            "b": _make_result("b", sharpe=1.5),
        }
        rrs, _, _, _, _, _ = compute_rrs(results)
        assert 0.0 <= rrs <= 100.0


@pytest.mark.unit
class TestVerdicts:
    def _make_report(self, rrs: float) -> RegimeRobustnessReport:
        from libs.backtest.scenarios.robustness import _verdict
        results = {"a": _make_result("a", sharpe=1.0)}
        return RegimeRobustnessReport(
            experiment_name="test",
            scenario_results=results,
            signal_integrity=50.0,
            breadth=50.0,
            drawdown_resilience=50.0,
            regime_transition=50.0,
            stability=50.0,
            rrs=rrs,
            verdict=_verdict(rrs),
        )

    def test_robust_verdict(self):
        r = self._make_report(75.0)
        assert r.verdict == "ROBUST"

    def test_fragile_verdict_lower_bound(self):
        r = self._make_report(40.0)
        assert r.verdict == "FRAGILE"

    def test_fragile_verdict_upper_bound(self):
        r = self._make_report(69.9)
        assert r.verdict == "FRAGILE"

    def test_overfit_verdict(self):
        r = self._make_report(39.9)
        assert r.verdict == "OVERFIT"

    def test_boundary_70_is_robust(self):
        r = self._make_report(70.0)
        assert r.verdict == "ROBUST"
