"""Known-case tests for the valuation engine."""
from __future__ import annotations

from app.engines.valuation import (
    BusinessProfile, CompanyFundamentalsPerShare, DCFAssumptions, ReferenceMultiples,
    ReferenceSource, Scenario, WACCInputs, blend_fair_values, cap_terminal_growth_at_risk_free,
    compute_expected_return, compute_margin_of_safety, compute_multiples_fair_values,
    compute_price_bands, compute_self_historical_reference_multiples, compute_wacc,
    run_all_scenarios, run_dcf,
)
from app.engines.valuation.dcf import (  # Part B1
    BEAR_SHIFTS_PP, EXPLICIT_YEARS, SCENARIO_MODE_ADDITIVE,
    SCENARIO_MODE_HYBRID, SCENARIO_MODE_MULTIPLICATIVE, derive_bear_bull, fade_path,
)
from app.engines.valuation.expected_return import ExpectedReturnComponents


def test_wacc_known_case():
    inputs = WACCInputs(
        risk_free_rate=0.04, beta=1.2, equity_risk_premium=0.05, cost_of_debt_pretax=0.05,
        tax_rate=0.25, market_cap=800.0, total_debt=200.0,
    )
    r = compute_wacc(inputs)
    # Cost of equity = 0.04 + 1.2*0.05 = 0.10 ; cost of debt after tax = 0.05*0.75 = 0.0375
    # E/V=0.8, D/V=0.2 -> WACC = 0.8*0.10 + 0.2*0.0375 = 0.08 + 0.0075 = 0.0875
    assert abs(r.value - 0.0875) < 1e-9
    # AUDIT (Part 9): both beta and cost of debt were disclosed/real -> ACTUAL, not ASSUMED.
    assert r.sources["beta"] == "ACTUAL"
    assert r.sources["cost_of_debt_pretax"] == "ACTUAL"
    assert r.sources["risk_free_rate"] == "ASSUMED"  # always assumed, even when disclosed as real
    assert len(r.assumption_notes) == 2  # risk_free_rate + equity_risk_premium only


def test_wacc_missing_beta_and_cost_of_debt_flagged_assumed():
    inputs = WACCInputs(
        risk_free_rate=0.04, beta=None, equity_risk_premium=0.05, cost_of_debt_pretax=None,
        tax_rate=0.25, market_cap=800.0, total_debt=200.0,
    )
    r = compute_wacc(inputs)
    assert r.sources["beta"] == "ASSUMED"
    assert r.beta_used == 1.0  # DEFAULT_BETA_IF_MISSING
    assert r.sources["cost_of_debt_pretax"] == "ASSUMED"
    assert any("Beta 1.00" in n for n in r.assumption_notes)
    assert len(r.assumption_notes) == 4  # risk-free, ERP, beta, cost of debt all assumed


def test_wacc_missing_market_cap_returns_none():
    inputs = WACCInputs(0.04, 1.0, 0.05, 0.05, 0.25, market_cap=None, total_debt=None)
    r = compute_wacc(inputs)
    assert r.value is None


def test_dcf_requires_wacc_greater_than_terminal_growth():
    a = DCFAssumptions(100, 0.05, 0.2, 0.25, 0.1, wacc=0.03, terminal_growth=0.03, diluted_shares=10, net_debt=0)
    r = run_dcf(Scenario.BASE, a)
    assert r.error is not None
    assert r.fair_value_per_share is None


def test_dcf_terminal_value_contribution_is_traceable():
    a = DCFAssumptions(
        starting_revenue=1000, revenue_growth_rate=0.05, operating_margin=0.20, tax_rate=0.25,
        reinvestment_rate=0.05, wacc=0.09, terminal_growth=0.02, diluted_shares=100, net_debt=0,
    )
    r = run_dcf(Scenario.BASE, a)
    pct = r.terminal_value_pct_of_enterprise_value
    assert pct is not None
    # Hand check: pct = pv_of_terminal_value / enterprise_value, both already asserted > 0 above
    assert abs(pct - (r.pv_of_terminal_value / r.enterprise_value)) < 1e-12
    assert 0.0 < pct < 1.0  # terminal value is real but shouldn't exceed 100% of EV by construction


def test_dcf_hand_computed_single_year_sanity():
    # Zero growth, zero terminal growth, 1-year horizon approximated by checking year-1 FCFF math
    a = DCFAssumptions(
        starting_revenue=1000, revenue_growth_rate=0.0, operating_margin=0.20, tax_rate=0.25,
        reinvestment_rate=0.05, wacc=0.10, terminal_growth=0.02, diluted_shares=100, net_debt=0,
    )
    r = run_dcf(Scenario.BASE, a)
    # Year 1: revenue=1000, EBIT=200, NOPAT=150, reinvestment=50, FCFF=100
    assert abs(r.fcff_path[0] - 100.0) < 1e-6
    assert r.enterprise_value > 0
    assert r.fair_value_per_share == r.equity_value / 100


def test_dcf_scenarios_bear_lower_than_bull():
    a = DCFAssumptions(1000, 0.06, 0.20, 0.25, 0.05, wacc=0.09, terminal_growth=0.02, diluted_shares=100, net_debt=0)
    scenarios = run_all_scenarios(a)
    bear_fv = scenarios[Scenario.BEAR].fair_value_per_share
    base_fv = scenarios[Scenario.BASE].fair_value_per_share
    bull_fv = scenarios[Scenario.BULL].fair_value_per_share
    assert bear_fv < base_fv < bull_fv


def test_terminal_growth_capped_at_risk_free():
    assert cap_terminal_growth_at_risk_free(0.06, 0.04) == 0.04
    assert cap_terminal_growth_at_risk_free(0.02, 0.04) == 0.02


def test_multiples_fair_value_pe():
    fundamentals = CompanyFundamentalsPerShare(eps=5.0, forward_eps=5.5, ebitda_per_share=8.0, fcf_per_share=4.0, net_debt_per_share=2.0)
    refs = ReferenceMultiples(pe=20.0, forward_pe=18.0, ev_to_ebitda=10.0, p_fcf=25.0, ev_to_fcf=22.0, source=ReferenceSource.INDUSTRY_MEDIAN)
    results = {r.metric: r.fair_value_per_share for r in compute_multiples_fair_values(fundamentals, refs)}
    assert results["pe"] == 100.0  # 5.0 * 20
    assert results["ev_to_ebitda"] == 8.0 * 10.0 - 2.0  # 78.0


def test_multiples_negative_eps_excluded():
    fundamentals = CompanyFundamentalsPerShare(eps=-1.0, forward_eps=None, ebitda_per_share=None, fcf_per_share=None, net_debt_per_share=0)
    refs = ReferenceMultiples(pe=20.0, forward_pe=None, ev_to_ebitda=None, p_fcf=None, ev_to_fcf=None, source=ReferenceSource.PEER_MEDIAN)
    results = {r.metric: r.fair_value_per_share for r in compute_multiples_fair_values(fundamentals, refs)}
    assert results["pe"] is None


def test_blend_is_not_a_plain_average():
    blended = blend_fair_values(
        dcf_bear=80, dcf_base=100, dcf_bull=130,
        multiples_fair_values={"pe": 140, "ev_to_ebitda": 150},
        profile=BusinessProfile.STABLE_FCF,  # weights 0.6 DCF / 0.4 multiples
    )
    plain_average = (100 + 145) / 2  # 122.5
    weighted = 0.6 * 100 + 0.4 * 145  # 118.0
    assert abs(blended.weighted_fair_value - weighted) < 1e-9
    assert abs(blended.weighted_fair_value - plain_average) > 1.0  # demonstrably not a plain average


def test_blend_financials_favors_multiples_heavily():
    blended = blend_fair_values(
        dcf_bear=80, dcf_base=100, dcf_bull=130, multiples_fair_values={"pe": 200},
        profile=BusinessProfile.FINANCIALS,
    )
    assert blended.dcf_weight == 0.1
    assert blended.multiples_weight == 0.9


def test_margin_of_safety_and_price_bands():
    mos = compute_margin_of_safety(price=80, fair_value=100)
    assert abs(mos - 0.20) < 1e-9
    bands = compute_price_bands(fair_value=100, price=80, risk_score_0_100=100)  # very low risk -> tighter bands
    assert bands.strong_buy_price < bands.buy_price < bands.fair_value < bands.overvalued_price
    high_risk_bands = compute_price_bands(fair_value=100, price=80, risk_score_0_100=0)
    # Higher risk requires a WIDER margin of safety -> lower strong buy price
    assert high_risk_bands.strong_buy_price < bands.strong_buy_price


def test_self_historical_reference_multiples_needs_min_periods():
    # AUDIT (Part 12): fewer than 3 real historical points -> None (NOT AVAILABLE), never a guess.
    refs = compute_self_historical_reference_multiples({"pe": [18.0, 20.0]})
    assert refs.pe is None
    assert refs.source == ReferenceSource.SELF_HISTORICAL_5Y_MEDIAN


def test_self_historical_reference_multiples_known_median():
    refs = compute_self_historical_reference_multiples({
        "pe": [15.0, 18.0, 21.0, 24.0],       # median of 4 -> (18+21)/2 = 19.5
        "ev_to_ebitda": [10.0, 12.0, 14.0],    # median of 3 -> 12.0
    })
    assert abs(refs.pe - 19.5) < 1e-9
    assert abs(refs.ev_to_ebitda - 12.0) < 1e-9
    assert refs.forward_pe is None  # no data supplied at all for this key
    assert refs.p_fcf is None
    assert refs.ev_to_fcf is None


def test_expected_return_known_case():
    c = ExpectedReturnComponents(
        fundamental_growth_rate=0.10, shareholder_yield=0.02, current_multiple=20, reference_multiple=20, years=5,
    )
    r = compute_expected_return(c)
    assert abs(r.cagr - (1.10 * 1.02 - 1)) < 1e-9


# --- Part B1: DCF growth/margin trajectories and scenario derivation ---


def _base(**kw):
    d = dict(starting_revenue=1000.0, revenue_growth_rate=0.06, operating_margin=0.20,
             tax_rate=0.25, reinvestment_rate=0.05, wacc=0.09, terminal_growth=0.02,
             diluted_shares=100.0, net_debt=0.0)
    d.update(kw)
    return DCFAssumptions(**d)


def test_fade_path_linear_from_start_to_end():
    p = fade_path(0.20, 0.02, years=10)
    assert len(p) == 10
    assert abs(p[0] - (0.20 + (0.02 - 0.20) * 0.1)) < 1e-12
    assert abs(p[-1] - 0.02) < 1e-12
    # strictly decreasing all the way down
    assert all(p[i] > p[i + 1] for i in range(len(p) - 1))


def test_fade_path_reaches_end_early_then_holds():
    """The margin-ramp shape: 'reaches mature level by year 5, then holds'."""
    p = fade_path(0.10, 0.30, years=10, fade_years=5)
    assert abs(p[4] - 0.30) < 1e-12
    assert p[5:] == [0.30] * 5


def test_fade_path_with_equal_start_and_end_is_flat():
    assert fade_path(0.05, 0.05, years=10) == [0.05] * 10


def test_no_path_supplied_is_identical_to_the_flat_scalar():
    """Backward compatibility, proven rather than asserted: a DCF with no paths must produce
    exactly the same numbers as one whose paths are the flat scalar repeated."""
    flat = run_dcf(Scenario.BASE, _base())
    explicit = run_dcf(Scenario.BASE, _base(
        revenue_growth_path=[0.06] * EXPLICIT_YEARS,
        operating_margin_path=[0.20] * EXPLICIT_YEARS,
    ))
    assert flat.fair_value_per_share == explicit.fair_value_per_share
    assert flat.fcff_path == explicit.fcff_path


def test_growth_path_is_actually_used_year_by_year():
    """Hand-computed: revenue 1000, growth path [0.10, 0.00, ...] -> revenue is 1100 in year 1 and
    still 1100 in year 2, so year-2 FCFF equals year-1 FCFF exactly."""
    path = [0.10, 0.0] + [0.0] * (EXPLICIT_YEARS - 2)
    r = run_dcf(Scenario.BASE, _base(revenue_growth_path=path))
    assert abs(r.fcff_path[0] - r.fcff_path[1]) < 1e-9
    # year 1: revenue 1100, ebit 220, nopat 165, reinvestment 55 -> fcff 110
    assert abs(r.fcff_path[0] - 110.0) < 1e-9


def test_margin_ramp_raises_fair_value_versus_a_flat_low_margin():
    ramp = fade_path(0.10, 0.30, years=EXPLICIT_YEARS, fade_years=5)
    ramped = run_dcf(Scenario.BASE, _base(operating_margin=0.10, operating_margin_path=ramp))
    flat = run_dcf(Scenario.BASE, _base(operating_margin=0.10))
    assert ramped.fair_value_per_share > flat.fair_value_per_share


def test_wrong_length_path_is_refused_not_silently_truncated():
    """A truncated forecast is a wrong valuation that looks completely normal."""
    r = run_dcf(Scenario.BASE, _base(revenue_growth_path=[0.05, 0.05, 0.05]))
    assert r.fair_value_per_share is None
    assert "expected" in (r.error or "")


def test_additive_scenario_shifts_are_comparable_across_base_growth_rates():
    """The defect Part B1 fixes: a fixed multiplier gives a mature company and a hypergrowth
    company the same RELATIVE band. Additive pp shifts give them the same ABSOLUTE band, which is
    what 'how much slower could this plausibly grow' actually means."""
    mature_bear, _ = derive_bear_bull(_base(revenue_growth_rate=0.03), mode=SCENARIO_MODE_ADDITIVE)
    hyper_bear, _ = derive_bear_bull(_base(revenue_growth_rate=0.30), mode=SCENARIO_MODE_ADDITIVE)
    mature_gap = 0.03 - mature_bear.revenue_growth_rate
    hyper_gap = 0.30 - hyper_bear.revenue_growth_rate
    assert abs(mature_gap - hyper_gap) < 1e-12
    assert abs(mature_gap - abs(BEAR_SHIFTS_PP["revenue_growth_rate"])) < 1e-12


def test_multiplicative_mode_reproduces_the_pre_b1_relative_spread():
    """The old behaviour is retained, not deleted -- a deployment can still reproduce it."""
    mature_bear, _ = derive_bear_bull(_base(revenue_growth_rate=0.03),
                                      mode=SCENARIO_MODE_MULTIPLICATIVE)
    hyper_bear, _ = derive_bear_bull(_base(revenue_growth_rate=0.30),
                                     mode=SCENARIO_MODE_MULTIPLICATIVE)
    assert abs(mature_bear.revenue_growth_rate - 0.03 * 0.6) < 1e-12
    assert abs(hyper_bear.revenue_growth_rate - 0.30 * 0.6) < 1e-12


def test_unknown_scenario_mode_is_rejected():
    try:
        derive_bear_bull(_base(), mode="vibes")
    except ValueError:
        return
    raise AssertionError("an unknown scenario mode was accepted")


def test_scenario_shifts_apply_to_paths_not_just_the_scalar():
    """Silent-no-op guard: with a path supplied, shifting only the scalar would leave Bear
    identical to Base."""
    base = _base(revenue_growth_path=fade_path(0.20, 0.02, years=EXPLICIT_YEARS))
    bear, bull = derive_bear_bull(base)
    assert bear.revenue_growth_path != base.revenue_growth_path
    assert all(b < s for b, s in zip(bear.revenue_growth_path, base.revenue_growth_path))
    assert all(b > s for b, s in zip(bull.revenue_growth_path, base.revenue_growth_path))
    bear_r = run_dcf(Scenario.BEAR, bear)
    base_r = run_dcf(Scenario.BASE, base)
    assert bear_r.fair_value_per_share < base_r.fair_value_per_share


def test_bear_margin_path_is_floored_at_zero():
    base = _base(operating_margin=0.01, operating_margin_path=[0.01] * EXPLICIT_YEARS)
    bear, _ = derive_bear_bull(base)
    assert all(m >= 0.0 for m in bear.operating_margin_path)


def test_terminal_growth_stays_below_wacc_in_both_modes():
    for mode in (SCENARIO_MODE_HYBRID, SCENARIO_MODE_ADDITIVE, SCENARIO_MODE_MULTIPLICATIVE):
        _, bull = derive_bear_bull(_base(terminal_growth=0.088, wacc=0.09), mode=mode)
        assert bull.terminal_growth < 0.09


def test_bear_below_base_below_bull_still_holds_with_paths_and_additive_mode():
    base = _base(revenue_growth_path=fade_path(0.15, 0.03, years=EXPLICIT_YEARS),
                 operating_margin_path=fade_path(0.12, 0.25, years=EXPLICIT_YEARS, fade_years=5))
    results = run_all_scenarios(base)
    bear = results[Scenario.BEAR].fair_value_per_share
    mid = results[Scenario.BASE].fair_value_per_share
    bull = results[Scenario.BULL].fair_value_per_share
    assert bear < mid < bull


def _spread(growth, mode):
    b = _base(revenue_growth_rate=growth,
              revenue_growth_path=fade_path(growth, 0.025, years=EXPLICIT_YEARS))
    r = run_all_scenarios(b, mode=mode)
    v = {k.value: r[k].fair_value_per_share for k in r}
    return (v["BULL"] - v["BEAR"]) / v["BASE"]


def test_hybrid_mode_gives_mature_a_narrower_band_than_hypergrowth():
    """The actual goal docs/AUDIT_VALUATION.md stated. Asserted on measured output, not on the
    shape of the formula."""
    assert _spread(0.03, SCENARIO_MODE_HYBRID) < _spread(0.10, SCENARIO_MODE_HYBRID)
    assert _spread(0.10, SCENARIO_MODE_HYBRID) < _spread(0.30, SCENARIO_MODE_HYBRID)


def test_additive_mode_band_is_flat_across_growth_rates():
    """Documents why additive_pp is NOT the default: its relative band is the same for a 3% grower
    and a 30% grower, which means it is too wide for the mature case."""
    assert abs(_spread(0.03, SCENARIO_MODE_ADDITIVE) - _spread(0.30, SCENARIO_MODE_ADDITIVE)) < 0.05


def test_hybrid_uses_the_floor_for_a_low_growth_base():
    """3% base growth: 30% of 3% is 0.9pp, below the 2pp floor, so the floor applies."""
    bear, _ = derive_bear_bull(_base(revenue_growth_rate=0.03), mode=SCENARIO_MODE_HYBRID)
    assert abs(bear.revenue_growth_rate - 0.01) < 1e-12


def test_hybrid_uses_the_proportional_term_for_a_high_growth_base():
    """30% base growth: 30% of 30% is 9pp, above the 2pp floor, so the proportional term applies."""
    bear, _ = derive_bear_bull(_base(revenue_growth_rate=0.30), mode=SCENARIO_MODE_HYBRID)
    assert abs(bear.revenue_growth_rate - 0.21) < 1e-12


def test_hybrid_handles_a_negative_base_growth_rate():
    """A shrinking business must get a WIDER-in-magnitude downside, not a sign-flipped shift."""
    bear, bull = derive_bear_bull(_base(revenue_growth_rate=-0.10), mode=SCENARIO_MODE_HYBRID)
    assert bear.revenue_growth_rate < -0.10 < bull.revenue_growth_rate


def test_default_scenario_mode_is_hybrid():
    from app.engines.valuation.dcf import DEFAULT_SCENARIO_MODE
    assert DEFAULT_SCENARIO_MODE == SCENARIO_MODE_HYBRID


# --- Part B2: industry-median reference multiples ---


def _self_refs(**kw):
    from app.engines.valuation.multiples import ReferenceMultiples, ReferenceSource
    d = dict(pe=20.0, forward_pe=18.0, ev_to_ebitda=12.0, p_fcf=25.0, ev_to_fcf=22.0,
             source=ReferenceSource.SELF_HISTORICAL_5Y_MEDIAN)
    d.update(kw)
    return ReferenceMultiples(**d)


def test_combine_prefers_industry_median_by_default():
    from app.engines.valuation.multiples import ReferenceSource, combine_reference_multiples

    out = combine_reference_multiples(_self_refs(), {"pe": 15.0, "ev_to_ebitda": 9.0})
    assert out.pe == 15.0
    assert out.ev_to_ebitda == 9.0
    assert out.forward_pe == 18.0  # no industry median for this field -> self-historical
    assert out.source == ReferenceSource.MIXED_INDUSTRY_AND_SELF
    assert out.per_field_source["pe"] == ReferenceSource.INDUSTRY_MEDIAN
    assert out.per_field_source["forward_pe"] == ReferenceSource.SELF_HISTORICAL_5Y_MEDIAN


def test_combine_with_no_industry_medians_is_the_pre_b2_behaviour():
    from app.engines.valuation.multiples import combine_reference_multiples

    assert combine_reference_multiples(_self_refs(), None) == _self_refs()
    assert combine_reference_multiples(_self_refs(), {}) == _self_refs()


def test_combine_self_only_preference_ignores_industry_medians():
    from app.engines.valuation.multiples import PREFERENCE_SELF_ONLY, combine_reference_multiples

    out = combine_reference_multiples(_self_refs(), {"pe": 15.0}, preference=PREFERENCE_SELF_ONLY)
    assert out.pe == 20.0


def test_combine_self_then_industry_uses_industry_only_as_a_fallback():
    from app.engines.valuation.multiples import (
        PREFERENCE_SELF_THEN_INDUSTRY, ReferenceSource, combine_reference_multiples)

    out = combine_reference_multiples(
        _self_refs(pe=None), {"pe": 15.0, "forward_pe": 11.0},
        preference=PREFERENCE_SELF_THEN_INDUSTRY,
    )
    assert out.pe == 15.0                      # own history had none -> industry fills in
    assert out.forward_pe == 18.0              # own history had one -> own wins
    assert out.per_field_source["pe"] == ReferenceSource.INDUSTRY_MEDIAN


def test_combine_when_every_field_comes_from_industry_reports_a_single_source():
    from app.engines.valuation.multiples import ReferenceSource, combine_reference_multiples

    out = combine_reference_multiples(
        _self_refs(pe=None, forward_pe=None, ev_to_ebitda=None, p_fcf=None, ev_to_fcf=None),
        {"pe": 15.0, "forward_pe": 13.0, "ev_to_ebitda": 9.0, "p_fcf": 19.0, "ev_to_fcf": 17.0},
    )
    assert out.source == ReferenceSource.INDUSTRY_MEDIAN
    assert out.per_field_source is not None


def test_combine_field_missing_from_both_stays_none():
    from app.engines.valuation.multiples import combine_reference_multiples

    out = combine_reference_multiples(_self_refs(p_fcf=None), {"pe": 15.0})
    assert out.p_fcf is None


# --- Part B3: Fair Value end-to-end audit fixes ---


def test_ev_multiple_returns_none_when_net_debt_exceeds_enterprise_value():
    """A positive fundamental can still produce a NEGATIVE per-share equity value once net debt is
    subtracted. That is a real economic statement, but it is not a fair value per share, and it
    used to flow straight into the blend average and the price bands."""
    from app.engines.valuation.multiples import (
        CompanyFundamentalsPerShare, ReferenceMultiples, ReferenceSource,
        compute_multiples_fair_values)

    f = CompanyFundamentalsPerShare(eps=None, forward_eps=None, ebitda_per_share=1.0,
                                     fcf_per_share=None, net_debt_per_share=8.0)
    refs = ReferenceMultiples(pe=None, forward_pe=None, ev_to_ebitda=5.0, p_fcf=None,
                              ev_to_fcf=None, source=ReferenceSource.SELF_HISTORICAL_5Y_MEDIAN)
    out = {r.metric: r.fair_value_per_share for r in compute_multiples_fair_values(f, refs)}
    assert out["ev_to_ebitda"] is None  # 1.0 * 5.0 - 8.0 = -3.0, refused


def test_ev_multiple_still_returns_a_positive_value_normally():
    from app.engines.valuation.multiples import (
        CompanyFundamentalsPerShare, ReferenceMultiples, ReferenceSource,
        compute_multiples_fair_values)

    f = CompanyFundamentalsPerShare(eps=None, forward_eps=None, ebitda_per_share=2.0,
                                     fcf_per_share=None, net_debt_per_share=1.0)
    refs = ReferenceMultiples(pe=None, forward_pe=None, ev_to_ebitda=5.0, p_fcf=None,
                              ev_to_fcf=None, source=ReferenceSource.SELF_HISTORICAL_5Y_MEDIAN)
    out = {r.metric: r.fair_value_per_share for r in compute_multiples_fair_values(f, refs)}
    assert out["ev_to_ebitda"] == 9.0


def test_multiple_fair_value_reports_its_own_per_field_source():
    from app.engines.valuation.multiples import (
        CompanyFundamentalsPerShare, ReferenceMultiples, ReferenceSource,
        compute_multiples_fair_values)

    refs = ReferenceMultiples(
        pe=15.0, forward_pe=18.0, ev_to_ebitda=None, p_fcf=None, ev_to_fcf=None,
        source=ReferenceSource.MIXED_INDUSTRY_AND_SELF,
        per_field_source={"pe": ReferenceSource.INDUSTRY_MEDIAN,
                          "forward_pe": ReferenceSource.SELF_HISTORICAL_5Y_MEDIAN},
    )
    f = CompanyFundamentalsPerShare(eps=2.0, forward_eps=2.5, ebitda_per_share=None,
                                     fcf_per_share=None, net_debt_per_share=None)
    by_metric = {r.metric: r.reference_source for r in compute_multiples_fair_values(f, refs)}
    assert by_metric["pe"] == ReferenceSource.INDUSTRY_MEDIAN
    assert by_metric["forward_pe"] == ReferenceSource.SELF_HISTORICAL_5Y_MEDIAN


def test_price_bands_refuse_a_non_positive_fair_value():
    """Before Part B3 a negative fair value produced a complete, negative price ladder."""
    from app.engines.valuation import compute_price_bands

    for bad in (-10.0, 0.0):
        bands = compute_price_bands(bad, price=5.0)
        assert bands.strong_buy_price is None
        assert bands.buy_price is None
        assert bands.overvalued_price is None
        assert bands.margin_of_safety is None


def test_multiples_blend_uses_the_median_once_three_are_available():
    """One outlier multiple used to move the blended Fair Value -- and therefore the margin of
    safety, the price bands and the recommendation -- with no visible cause."""
    from app.engines.valuation.blend import BusinessProfile, blend_fair_values

    tight = {"pe": 100.0, "forward_pe": 102.0, "p_fcf": 98.0}
    with_outlier = {"pe": 100.0, "forward_pe": 102.0, "p_fcf": 98.0, "ev_to_fcf": 500.0}
    a = blend_fair_values(None, None, None, tight, BusinessProfile.STABLE_FCF)
    b = blend_fair_values(None, None, None, with_outlier, BusinessProfile.STABLE_FCF)
    # mean of the four would be 200.0; the median is 101.0
    assert a.weighted_fair_value == 100.0
    assert b.weighted_fair_value == 101.0


def test_multiples_blend_uses_the_mean_below_three_values():
    from app.engines.valuation.blend import BusinessProfile, blend_fair_values

    out = blend_fair_values(None, None, None, {"pe": 10.0, "forward_pe": 20.0},
                            BusinessProfile.STABLE_FCF)
    assert out.weighted_fair_value == 15.0


def test_blend_confidence_falls_with_data_completeness():
    """`data_completeness_pct` defaults to 100.0 and the only call site in the application never
    passed it, so every security's blended-Fair-Value confidence started from a perfect score."""
    from app.engines.valuation.blend import BusinessProfile, blend_fair_values

    full = blend_fair_values(None, 100.0, None, {"pe": 100.0}, BusinessProfile.STABLE_FCF,
                             data_completeness_pct=100.0)
    thin = blend_fair_values(None, 100.0, None, {"pe": 100.0}, BusinessProfile.STABLE_FCF,
                             data_completeness_pct=30.0)
    assert full.confidence > thin.confidence
    assert thin.confidence == 30.0  # no dispersion (dcf == multiples), STABLE_FCF has no penalty


# --- final master pass: the `diluted_shares or 1` blast radius, recorded as executable fact ---


def test_dcf_refuses_a_missing_share_count_rather_than_valuing_one_share():
    """`app/workers/recompute.py` used to pass `diluted_shares=c.diluted_shares or 1`, which
    substituted 1 BEFORE run_dcf()'s own guard could see the missing value. This test pins both
    halves of why that mattered: the guard works, and defeating it produces an absurd number that
    still looks like a valid fair value."""
    refused = run_dcf(Scenario.BASE, _base(diluted_shares=None))
    assert refused.fair_value_per_share is None
    assert "shares" in (refused.error or "").lower()

    # What the old `or 1` produced instead: the entire equity value, presented per "share".
    one_share = run_dcf(Scenario.BASE, _base(diluted_shares=1.0))
    hundred_shares = run_dcf(Scenario.BASE, _base(diluted_shares=100.0))
    assert one_share.fair_value_per_share is not None
    assert abs(one_share.fair_value_per_share - hundred_shares.fair_value_per_share * 100) < 1e-6
    # ...i.e. a fair value 100x the real one for this company, and unbounded in general.


def test_dcf_refuses_a_non_positive_share_count():
    for bad in (0.0, -5.0):
        assert run_dcf(Scenario.BASE, _base(diluted_shares=bad)).fair_value_per_share is None


def test_unknown_debt_treated_as_zero_would_overstate_equity_value():
    """The second half of the same recompute line: `net_debt=(c.total_debt or 0) - cash` turned an
    UNKNOWN debt balance into a net-cash position. This shows the direction and size of the error
    the fix removes -- it is always optimistic."""
    with_real_debt = run_dcf(Scenario.BASE, _base(net_debt=400.0))
    as_if_debt_free = run_dcf(Scenario.BASE, _base(net_debt=-100.0))  # cash only, debt "unknown"
    assert as_if_debt_free.fair_value_per_share > with_real_debt.fair_value_per_share
    # the gap is exactly the mis-stated net debt spread over the share count
    assert abs((as_if_debt_free.fair_value_per_share - with_real_debt.fair_value_per_share)
               - 500.0 / 100.0) < 1e-9


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)
