"""
End-to-end engine validation against the 8 real-shaped company fixtures (StockLab overhaul, Part
24): large cap, small cap, bank, REIT, cyclical, high-growth/unprofitable, negative-FCF, and
high-debt. Runs each through metrics -> WACC -> DCF -> scoring, asserting no crashes and the
specific sector/shape-dependent behavior each fixture exists to exercise. No live API calls —
all data is synthetic and hand-built in tests/fixtures/company_profiles.py.
"""
from __future__ import annotations

from app.engines.metrics.core import compute_all_metrics
from app.engines.quality.accounting import assess_accounting_quality
from app.engines.scoring.subscores import compute_financial_health_score, compute_quality_score
from app.engines.valuation import WACCInputs, compute_wacc
from tests.fixtures.company_profiles import ALL_FIXTURES


def test_every_fixture_computes_all_metrics_without_crashing():
    for name, build in ALL_FIXTURES.items():
        snap = build()
        metrics = compute_all_metrics(snap)
        assert len(metrics) > 20, f"{name}: expected the full metric set, got {len(metrics)}"
        # Never a silent 0 for missing/NM inputs anywhere in the fixture set.
        for key, result in metrics.items():
            if result.status.value == "MISSING":
                assert result.value is None, f"{name}.{key}: MISSING status but value != None"


def test_bank_fixture_gates_debt_ebitda_family_as_not_meaningful():
    metrics = compute_all_metrics(ALL_FIXTURES["bank"]())
    for key in ("debt_to_ebitda", "net_debt_to_ebitda", "current_ratio", "ev_to_ebitda"):
        assert metrics[key].applicability.value == "NOT_MEANINGFUL", f"{key} should be N/M for a bank"
        assert metrics[key].value is None


def test_reit_fixture_has_high_leverage_and_high_dividend_yield():
    metrics = compute_all_metrics(ALL_FIXTURES["reit"]())
    assert metrics["debt_to_equity"].value > 1.0  # REITs are structurally leveraged
    assert metrics["dividend_yield"].value > 0.05  # high payout is the REIT archetype


def test_cyclical_fixture_shows_a_down_year_in_history_not_smoothed_away():
    snap = ALL_FIXTURES["cyclical"]()
    assert snap.history[0].net_income < 0  # the down year is really there, not sanitized


def test_high_growth_unprofitable_fixture_has_negative_margins_and_nm_valuation_ratios():
    metrics = compute_all_metrics(ALL_FIXTURES["high_growth_unprofitable"]())
    assert metrics["net_margin"].value < 0
    assert metrics["pe"].value is None  # negative EPS -> N/M, never a nonsense negative P/E
    assert metrics["p_fcf"].value is None  # negative FCF -> N/M


def test_negative_fcf_reinvestor_fixture_is_profitable_but_fcf_negative():
    metrics = compute_all_metrics(ALL_FIXTURES["negative_fcf_reinvestor"]())
    assert metrics["net_margin"].value > 0  # profitable on an income-statement basis
    assert metrics["fcf"].value < 0         # but cash-negative once capex is netted out
    assert metrics["fcf_payout_ratio"].value is None  # N/M per the FCF<=0 guard


def test_high_debt_fixture_shows_elevated_leverage_and_thin_coverage():
    metrics = compute_all_metrics(ALL_FIXTURES["high_debt_leveraged"]())
    assert metrics["debt_to_ebitda"].value > 5.0
    assert metrics["interest_coverage"].value < 2.0
    flags = assess_accounting_quality(ALL_FIXTURES["high_debt_leveraged"]())
    # Debt grew YoY in this fixture (1800 -> 2100, +16.7%) -- below the 30% red-flag threshold by
    # design (this fixture's point is elevated STEADY-STATE leverage, not a debt-growth spike),
    # so this asserts the red-flag module correctly does NOT fire a debt_growth flag here,
    # distinguishing "high but stable" from "rapidly growing" leverage.
    assert "debt_growth" not in [f.key for f in flags.flags]


def test_wacc_computes_for_every_fixture_with_full_balance_sheet_data():
    for name, build in ALL_FIXTURES.items():
        snap = build()
        c = snap.current
        result = compute_wacc(WACCInputs(
            risk_free_rate=0.045, beta=None, equity_risk_premium=0.045,
            cost_of_debt_pretax=(c.interest_expense / c.total_debt) if c.interest_expense and c.total_debt else None,
            tax_rate=0.21, market_cap=c.market_cap, total_debt=c.total_debt,
        ))
        assert result.value is not None, f"{name}: WACC should compute (market_cap and total_debt are always set)"
        assert 0.0 < result.value < 0.30, f"{name}: WACC {result.value} outside a sane range"


def test_scoring_engine_reports_insufficient_data_when_there_are_no_peers():
    """HISTORICAL NOTE, preserved rather than deleted. Writing this test during the 0.2.0 overhaul
    (Part 24) surfaced a real finding: an empty peer set did NOT produce INSUFFICIENT_DATA --
    `percentile_rank()` returns a neutral 50.0 for an empty `peers` list, so every metric with no
    peer data landed as CALCULATED with value 50.0, indistinguishable in `status` from a subscore
    computed against a full, real peer group. The only tell was
    `ScoreResult.peer_group_tier == "insufficient_peer_group"`. That was documented in
    docs/AUDIT_PROVIDER_RESILIENCE.md and deliberately not changed at the time, because changing
    percentile semantics deserved its own review rather than being a side effect of a fixture test.

    UPDATED (final engineering pass, Part B2 — docs/AUDIT_PEER_GROUPS_B2.md). That review
    happened, and the finding turned out to be worse than recorded: nothing in the application
    ever supplied `peer_metric_values` at all, so this was not an edge case, it was every
    security's every score. `compute_generic_subscore()` now EXCLUDES a metric with no peers at
    any tier, so a subscore with nothing to compare against is honestly INSUFFICIENT_DATA instead
    of a confident-looking 50. This test now asserts the corrected behaviour; the assertions it
    used to make are quoted in the note above so the change is visible rather than silent."""
    for name, build in ALL_FIXTURES.items():
        snap = build()
        metrics = compute_all_metrics(snap)
        quality = compute_quality_score(metrics, {})
        fin_health = compute_financial_health_score(metrics, {})
        assert quality.status == "INSUFFICIENT_DATA", name
        assert quality.value is None, name
        assert fin_health.status == "INSUFFICIENT_DATA", name
        # every metric the fixture DID have a value for is reported as excluded-for-lack-of-peers,
        # not silently dropped
        assert quality.metrics_excluded, name


def test_scoring_engine_runs_for_every_fixture_with_a_real_peer_group():
    """The complement of the test above: given a real peer universe, the same fixtures produce
    real, differentiated percentile scores rather than a uniform number."""
    from app.engines.scoring.peer_groups import PeerMetricRow, build_peer_metric_values
    from app.engines.scoring.subscores import QUALITY_METRICS

    # The 8 fixtures ARE the peer universe here -- a self-consistent, real-valued comparison set
    # rather than invented peer numbers on an arbitrary scale.
    rows = []
    for name, build in ALL_FIXTURES.items():
        for key, result in compute_all_metrics(build()).items():
            if key in QUALITY_METRICS and result.value is not None:
                rows.append(PeerMetricRow(name, key, result.value, "IND", "SEC", "LARGE"))

    values = {}
    for name, build in ALL_FIXTURES.items():
        peers = build_peer_metric_values(rows, name, "IND", "SEC", "LARGE").as_dict()
        quality = compute_quality_score(compute_all_metrics(build()), peers)
        if quality.value is not None:
            values[name] = quality.value

    assert values, "no fixture produced a quality score against a real peer group"
    assert len(set(values.values())) > 1, (
        f"every fixture scored identically -- peers are not being used: {values}")
    assert all(0.0 <= v <= 100.0 for v in values.values()), values


ALL_TESTS = [obj for name, obj in list(globals().items()) if name.startswith("test_") and callable(obj)]

if __name__ == "__main__":
    passed, failed = 0, []
    for fn in ALL_TESTS:
        try:
            fn()
            passed += 1
            print(f"PASS  {fn.__name__}")
        except AssertionError as e:
            failed.append(fn.__name__)
            print(f"FAIL  {fn.__name__}: {e}")
    print(f"\n{passed}/{len(ALL_TESTS)} passed")
    if failed:
        raise SystemExit(1)
