"""
Forward estimate selection (final master pass, §21).

§21's rules, each one a separate way a forward P/E can be quietly wrong:
look-ahead (using a consensus published after the decision date), staleness (an abandoned
consensus), and a "forward" estimate for a period that has already ended. Plus the categorical
rule that a StockLab assumption must never be presentable as analyst consensus.

Pure, dependency-free — genuinely executed. Dual-mode: pytest, or
`PYTHONPATH=. python3 tests/test_estimates.py`.
"""
from __future__ import annotations

from datetime import date

from app.engines.estimates import (
    MAX_ESTIMATE_AGE_DAYS,
    ORIGIN_PROVIDER_CONSENSUS,
    ORIGIN_STOCKLAB_ASSUMPTION,
    EstimateRecord,
    select_forward_estimate,
)

AS_OF = date(2025, 12, 31)


def _rec(period_end: str, as_of: str, value=2.5, metric="eps", analysts=12, origin=ORIGIN_PROVIDER_CONSENSUS):
    return EstimateRecord(
        metric=metric, period_end=date.fromisoformat(period_end), consensus_value=value,
        as_of_date=date.fromisoformat(as_of), num_analysts=analysts, source="TESTPROVIDER",
        origin=origin,
    )


# --- the happy path ---


def test_a_valid_forward_consensus_is_selected():
    sel = select_forward_estimate([_rec("2026-12-31", "2025-11-15")], AS_OF)
    assert sel.value == 2.5
    assert sel.is_consensus is True
    assert sel.reason is None
    assert sel.record.num_analysts == 12


def test_the_nearest_future_period_wins():
    """A forward P/E conventionally means the next fiscal year, not the furthest one available."""
    sel = select_forward_estimate([
        _rec("2028-12-31", "2025-12-01", value=9.9),
        _rec("2026-12-31", "2025-12-01", value=2.5),
        _rec("2027-12-31", "2025-12-01", value=5.0),
    ], AS_OF)
    assert sel.record.period_end == date(2026, 12, 31)
    assert sel.value == 2.5


def test_within_a_period_the_most_recent_consensus_wins():
    sel = select_forward_estimate([
        _rec("2026-12-31", "2025-03-01", value=1.0),
        _rec("2026-12-31", "2025-12-01", value=3.0),
    ], AS_OF)
    assert sel.value == 3.0
    assert sel.record.as_of_date == date(2025, 12, 1)


def test_a_tie_goes_to_the_estimate_with_more_analysts():
    sel = select_forward_estimate([
        _rec("2026-12-31", "2025-12-01", value=1.0, analysts=3),
        _rec("2026-12-31", "2025-12-01", value=2.0, analysts=25),
    ], AS_OF)
    assert sel.record.num_analysts == 25


# --- look-ahead bias ---


def test_an_estimate_published_after_the_calculation_date_is_refused():
    """The single easiest way to make a backtest look brilliant: use a consensus that did not
    exist on the day the decision was made."""
    sel = select_forward_estimate([_rec("2026-12-31", "2026-02-01")], AS_OF)
    assert sel.value is None
    assert any("after as_of" in r for r in sel.rejected)


def test_an_estimate_published_exactly_on_the_calculation_date_is_allowed():
    sel = select_forward_estimate([_rec("2026-12-31", "2025-12-31")], AS_OF)
    assert sel.value == 2.5


def test_look_ahead_rejection_does_not_hide_a_valid_older_estimate():
    sel = select_forward_estimate([
        _rec("2026-12-31", "2026-06-01", value=9.9),   # look-ahead
        _rec("2026-12-31", "2025-10-01", value=2.5),   # legitimate at as_of
    ], AS_OF)
    assert sel.value == 2.5


# --- "forward" must mean forward ---


def test_an_estimate_for_a_period_that_has_already_ended_is_refused():
    """A consensus for a finished period is not a forecast — the actual result has superseded it,
    and a P/E against it is a P/E against a number nobody expects any more."""
    sel = select_forward_estimate([_rec("2025-06-30", "2025-01-15")], AS_OF)
    assert sel.value is None
    assert any("already ended" in r for r in sel.rejected)


def test_an_estimate_for_a_period_ending_exactly_on_the_calculation_date_is_refused():
    sel = select_forward_estimate([_rec("2025-12-31", "2025-06-01")], AS_OF)
    assert sel.value is None


# --- staleness ---


def test_a_stale_consensus_is_refused_rather_than_used():
    """§21: "Forward metrics НЕ трябва да използват стари или неизвестни estimates." A forward P/E
    on an 18-month-old consensus is worse than no forward P/E."""
    sel = select_forward_estimate([_rec("2026-12-31", "2024-01-01")], AS_OF)
    assert sel.value is None
    assert any("days old" in r for r in sel.rejected)


def test_the_staleness_limit_is_configurable_and_inclusive_at_the_boundary():
    just_inside = _rec("2026-12-31", "2025-01-01")   # 364 days before AS_OF
    assert select_forward_estimate([just_inside], AS_OF).value == 2.5
    assert select_forward_estimate([just_inside], AS_OF, max_age_days=30).value is None
    assert MAX_ESTIMATE_AGE_DAYS == 365


# --- missing data is INSUFFICIENT DATA, never an invented number ---


def test_no_estimates_at_all_yields_none_with_a_reason():
    sel = select_forward_estimate([], AS_OF)
    assert sel.value is None
    assert "no eps estimates available at all" in (sel.reason or "")
    assert sel.candidates_considered == 0


def test_an_estimate_with_a_null_consensus_value_is_refused():
    sel = select_forward_estimate([_rec("2026-12-31", "2025-11-01", value=None)], AS_OF)
    assert sel.value is None
    assert any("no consensus value" in r for r in sel.rejected)


def test_every_rejection_is_reported_so_the_absence_is_explainable():
    sel = select_forward_estimate([
        _rec("2026-12-31", "2026-06-01"),   # look-ahead
        _rec("2025-06-30", "2025-01-01"),   # already ended
        _rec("2027-12-31", "2023-01-01"),   # stale
    ], AS_OF)
    assert sel.value is None
    assert len(sel.rejected) == 3
    assert sel.candidates_considered == 3


def test_a_different_metric_is_not_borrowed():
    """Asking for EPS must never return the revenue consensus."""
    sel = select_forward_estimate([_rec("2026-12-31", "2025-11-01", metric="revenue")], AS_OF)
    assert sel.value is None
    sel_rev = select_forward_estimate(
        [_rec("2026-12-31", "2025-11-01", metric="revenue")], AS_OF, metric="revenue")
    assert sel_rev.value == 2.5


# --- the categorical rule: an assumption is not a consensus ---


def test_a_stocklab_assumption_is_never_reported_as_analyst_consensus():
    """§21: "Никога не представяй StockLab assumption като analyst consensus." The distinction is
    representable in the data, so a UI can branch on it instead of guessing."""
    sel = select_forward_estimate(
        [_rec("2026-12-31", "2025-11-01", origin=ORIGIN_STOCKLAB_ASSUMPTION)], AS_OF)
    assert sel.value == 2.5                 # the number is still usable
    assert sel.is_consensus is False        # ...but it is not consensus
    assert sel.as_dict()["origin"] == ORIGIN_STOCKLAB_ASSUMPTION


def test_the_serialised_payload_carries_full_provenance():
    sel = select_forward_estimate([_rec("2026-12-31", "2025-11-15")], AS_OF)
    payload = sel.as_dict()
    for key in ("value", "metric", "period_end", "as_of_date", "num_analysts", "source",
                "origin", "is_consensus", "candidates_considered", "rejected"):
        assert key in payload, key
    assert payload["source"] == "TESTPROVIDER"


ALL_TESTS = [v for k, v in sorted(globals().items()) if k.startswith("test_")]

if __name__ == "__main__":
    passed = failed = 0
    for t in ALL_TESTS:
        try:
            t()
            print(f"PASS  {t.__name__}")
            passed += 1
        except Exception as exc:  # noqa: BLE001
            print(f"FAIL  {t.__name__}: {exc}")
            failed += 1
    print(f"\n{passed}/{passed + failed} passed")
    raise SystemExit(1 if failed else 0)
