"""
Known-answer tests for the 30-metric engine (spec §60: "Provери formulas with known test
cases"). Deliberately written as plain functions with only `assert` statements (no pytest
fixtures/parametrize) so this file is both a normal pytest test module AND directly executable
with plain `python3` for environments without pytest installed — see
`scripts/run_metric_tests_standalone.py`.
"""
from __future__ import annotations

from datetime import date

from app.engines.metrics.core import compute_all_metrics
from app.engines.types import FinancialSnapshot, LineItems, MarketCapBucket


def _li(period_end: str, **kwargs) -> LineItems:
    defaults = dict(
        security_id="TEST", period_end=date.fromisoformat(period_end), period_type="FY",
        filing_date=date.fromisoformat(period_end), currency="USD",
    )
    defaults.update(kwargs)
    return LineItems(**defaults)


def _snapshot(current: LineItems, history: list[LineItems], sector="DEFAULT", **kw) -> FinancialSnapshot:
    return FinancialSnapshot(
        security_id="TEST", industry_id="TEST_IND", sector_id=sector,
        market_cap_bucket=MarketCapBucket.LARGE, calculation_date=current.period_end,
        current=current, history=history, **kw,
    )


def test_gross_and_operating_margin():
    cur = _li("2025-12-31", revenue=1000, cogs=600, operating_income=250)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    assert abs(r["gross_margin"].value - 0.40) < 1e-9
    assert abs(r["operating_margin"].value - 0.25) < 1e-9


def test_roic_known_case():
    # EBIT 200, tax rate 25% (in-range) -> NOPAT 150. Invested capital = debt(300) + equity(500) - cash(50) = 750.
    cur = _li("2025-12-31", ebit=200, tax_expense=50, pretax_income=200,
              total_debt=300, shareholders_equity=500, cash_and_equivalents=50)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    assert r["roic"].value is not None
    assert abs(r["roic"].value - (150 / 750)) < 1e-9  # 0.20
    assert r["roic"].status.value == "CALCULATED"


def test_roic_out_of_range_tax_falls_back_to_default_and_flags_estimated():
    # tax_expense negative relative to pretax -> rate out of [0, 0.5] range -> fallback used.
    cur = _li("2025-12-31", ebit=100, tax_expense=90, pretax_income=100,
              total_debt=100, shareholders_equity=100, cash_and_equivalents=0)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    assert r["roic"].status.value == "ESTIMATED"
    assert abs(r["roic"].inputs_used["tax_rate"] - 0.25) < 1e-9


def test_net_debt_to_ebitda_flips_to_net_cash_note():
    cur = _li("2025-12-31", total_debt=100, cash_and_equivalents=400, ebitda=200)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    v = r["net_debt_to_ebitda"]
    assert v.value == -1.5
    assert "Net cash" in (v.note or "")


def test_interest_coverage_no_debt_no_interest_is_nm_not_infinite():
    cur = _li("2025-12-31", ebit=100, interest_expense=0, total_debt=0)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    assert r["interest_coverage"].value is None
    assert r["interest_coverage"].display == "N/M"


def test_pe_negative_eps_is_nm():
    cur = _li("2025-12-31", price=50, eps_diluted=-2.0)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    assert r["pe"].value is None


def test_fcf_payout_negative_fcf_is_nm_not_misleading_ratio():
    cur = _li("2025-12-31", operating_cash_flow=-50, capital_expenditure=20, dividends_paid=30)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    assert r["fcf"].value == -70
    assert r["fcf_payout_ratio"].value is None


def test_peg_requires_positive_growth():
    cur = _li("2025-12-31", price=100, eps_diluted=5.0)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap, peg_growth_pct=-5.0)
    assert r["peg"].value is None


def test_peg_known_case():
    cur = _li("2025-12-31", price=100, eps_diluted=5.0)  # P/E = 20
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap, peg_growth_pct=10.0)  # 10% growth
    assert abs(r["peg"].value - 2.0) < 1e-9


def test_revenue_growth_yoy_and_5y_cagr():
    cur = _li("2025-12-31", revenue=161.051)
    h = [
        _li("2024-12-31", revenue=146.41),
        _li("2023-12-31", revenue=133.10),
        _li("2022-12-31", revenue=121.00),
        _li("2021-12-31", revenue=110.00),
        _li("2020-12-31", revenue=100.00),
    ]
    snap = _snapshot(cur, h)
    r = compute_all_metrics(snap)
    assert abs(r["revenue_growth_yoy"].value - 0.10) < 1e-6
    assert abs(r["revenue_cagr_5y"].value - 0.10) < 1e-6


def test_dividend_cut_detected():
    cur = _li("2025-12-31", dividends_paid=50, diluted_shares=100)   # DPS 0.50
    h = [_li("2024-12-31", dividends_paid=100, diluted_shares=100)]  # DPS 1.00
    snap = _snapshot(cur, h)
    r = compute_all_metrics(snap)
    assert r["dividend_event"].note == "DIVIDEND_CUT"


def test_bank_sector_debt_to_ebitda_not_meaningful():
    cur = _li("2025-12-31", total_debt=1000, ebitda=200)
    snap = _snapshot(cur, [], sector="FINANCIALS")
    r = compute_all_metrics(snap)
    assert r["debt_to_ebitda"].value is None
    assert r["debt_to_ebitda"].applicability.value == "NOT_MEANINGFUL"
    assert r["debt_to_ebitda"].display == "N/M"


def test_missing_input_is_missing_not_zero():
    cur = _li("2025-12-31", revenue=None, cogs=None)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    assert r["gross_margin"].value is None
    assert r["gross_margin"].status.value == "MISSING"
    # Critically: never silently 0
    assert r["gross_margin"].value != 0


# ---------------------------------------------------------------------------
# StockLab overhaul audit (Part 2): known-answer + edge-case tests added for bugs found during
# independent re-verification of the 30-metric engine — see docs/AUDIT_METRICS.md.
# ---------------------------------------------------------------------------
def test_debt_to_ebitda_negative_ebitda_is_nm_not_negative_leverage():
    # AUDIT FIX regression test: positive debt over negative EBITDA must show N/M, not a
    # nonsensical negative "leverage" ratio that reads as less-levered than a healthy company.
    cur = _li("2025-12-31", total_debt=500, ebitda=-100)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    assert r["debt_to_ebitda"].value is None
    assert r["debt_to_ebitda"].display == "N/M"


def test_ev_to_ebitda_negative_ebitda_is_nm():
    cur = _li("2025-12-31", market_cap=1000, total_debt=200, cash_and_equivalents=50, ebitda=-40)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    assert r["ev_to_ebitda"].value is None


def test_net_debt_to_ebitda_negative_ebitda_positive_net_debt_is_nm_not_mislabeled_net_cash():
    # AUDIT FIX regression test: before the fix, positive net debt (300) / negative EBITDA (-50)
    # = -6.0, and the old note logic ("value < 0 => Net cash") mislabeled this net-DEBT company as
    # net-cash. It must now be N/M (EBITDA <= 0), not a mislabeled negative multiple.
    cur = _li("2025-12-31", total_debt=350, cash_and_equivalents=50, ebitda=-50)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    assert r["net_debt_to_ebitda"].value is None
    assert "Net cash" not in (r["net_debt_to_ebitda"].note or "")


def test_net_debt_to_ebitda_still_flags_real_net_cash_with_positive_ebitda():
    # Unchanged behavior for the case the original test already covered: positive EBITDA, net
    # cash position, note fires correctly (regression-proofs the fix didn't break the true case).
    cur = _li("2025-12-31", total_debt=100, cash_and_equivalents=400, ebitda=200)
    snap = _snapshot(cur, [])
    r = compute_all_metrics(snap)
    assert r["net_debt_to_ebitda"].value == -1.5
    assert "Net cash" in (r["net_debt_to_ebitda"].note or "")


def test_roe_negative_average_equity_is_nm_not_a_false_positive_return():
    # AUDIT FIX regression test: net income -20 / avg equity -100 previously gave ROE = +0.20 (a
    # "healthy" 20% return) for a company with a shareholders' deficit — exactly the failure mode
    # debt_to_equity already guarded against. Must now be N/M.
    cur = _li("2025-12-31", net_income=-20, shareholders_equity=-100)
    h = [_li("2024-12-31", shareholders_equity=-100)]
    snap = _snapshot(cur, h)
    r = compute_all_metrics(snap)
    assert r["roe"].value is None
    assert r["roe"].display == "N/M"


def test_roe_known_case_unaffected_by_fix():
    cur = _li("2025-12-31", net_income=50, shareholders_equity=500)
    h = [_li("2024-12-31", shareholders_equity=300)]
    snap = _snapshot(cur, h)
    r = compute_all_metrics(snap)
    # avg equity = (500+300)/2 = 400; ROE = 50/400 = 0.125
    assert abs(r["roe"].value - 0.125) < 1e-9


def test_debt_to_equity_known_case_and_negative_equity_guard():
    # AUDIT: debt_to_equity had zero test coverage before this audit despite FINANCIAL_FORMULAS.md
    # #9 explicitly documenting an equity<=0 -> N/M guard. Covers both the positive case and the
    # guard, hand-computed independently of the implementation.
    cur = _li("2025-12-31", total_debt=300, shareholders_equity=500)
    r = compute_all_metrics(_snapshot(cur, []))
    assert abs(r["debt_to_equity"].value - 0.6) < 1e-9  # 300/500
    cur_neg = _li("2025-12-31", total_debt=300, shareholders_equity=-10)
    r_neg = compute_all_metrics(_snapshot(cur_neg, []))
    assert r_neg["debt_to_equity"].value is None
    assert r_neg["debt_to_equity"].display == "N/M"


def test_batch_of_previously_untested_simple_ratio_metrics():
    # AUDIT: current_ratio, interest_coverage (positive case), pe/forward_pe/earnings_yield,
    # fcf/fcf_per_share/fcf_margin/fcf_yield/p_fcf/ev_to_fcf, dividend_yield/buyback_yield/
    # shareholder_yield/fcf_payout_ratio (positive case), net_margin, capex_to_revenue had ZERO
    # dedicated known-answer tests before this audit (see docs/AUDIT_METRICS.md) despite being
    # exercised indirectly only through edge-case tests elsewhere in this file. One shared
    # snapshot, every expected value hand-computed independently of core.py below.
    cur = _li(
        "2025-12-31",
        revenue=1000, net_income=150,
        ebit=200, interest_expense=40,
        current_assets=400, current_liabilities=200,
        total_debt=300, shareholders_equity=500, cash_and_equivalents=100,
        price=50.0, eps_diluted=2.5, diluted_shares=100, market_cap=5000.0,
        operating_cash_flow=300, capital_expenditure=50,
        dividends_paid=50, buybacks=80, stock_issuance=10,
    )
    snap = _snapshot(cur, [], forward_eps_estimate=3.0)
    r = compute_all_metrics(snap)

    assert abs(r["current_ratio"].value - 2.0) < 1e-9                # 400/200
    assert abs(r["interest_coverage"].value - 5.0) < 1e-9            # 200/40
    assert abs(r["pe"].value - 20.0) < 1e-9                          # 50/2.5
    assert abs(r["forward_pe"].value - (50 / 3.0)) < 1e-9
    assert abs(r["earnings_yield"].value - 0.05) < 1e-9              # 2.5/50
    assert abs(r["net_margin"].value - 0.15) < 1e-9                  # 150/1000
    assert abs(r["capex_to_revenue"].value - 0.05) < 1e-9            # 50/1000

    fcf = 300 - 50  # 250
    assert abs(r["fcf"].value - fcf) < 1e-9
    assert abs(r["fcf_per_share"].value - fcf / 100) < 1e-9
    assert abs(r["fcf_margin"].value - fcf / 1000) < 1e-9
    assert abs(r["fcf_yield"].value - fcf / 5000) < 1e-9
    assert abs(r["p_fcf"].value - 5000 / fcf) < 1e-9
    ev = 5000 + 300 - 100  # market_cap + debt - cash (no minority/preferred/ST-investments here)
    assert abs(r["ev_to_fcf"].value - ev / fcf) < 1e-9
    assert abs(r["fcf_payout_ratio"].value - 50 / fcf) < 1e-9        # dividends/FCF

    assert abs(r["dividend_yield"].value - 50 / 5000) < 1e-9         # 0.01
    assert abs(r["buyback_yield"].value - (80 - 10) / 5000) < 1e-9   # 0.014
    assert abs(r["shareholder_yield"].value - (0.01 + 0.014)) < 1e-8


def test_roic_uses_a_genuine_zero_percent_tax_rate_instead_of_falling_back():
    """REGRESSION (final master pass). `roic()` used to read
    `effective_tax_rate(c) or _fallback_tax_rate(snapshot)`. 0.0 is falsy in Python, so a company
    with a REAL, in-range 0% effective tax rate had that measurement silently discarded and the
    3-year average substituted -- while still reporting status CALCULATED. Loss-makers with tax
    credits, REITs, NOL carryforwards and tax holidays all legitimately report 0%.

    EBIT 200, invested capital = 300 + 500 - 50 = 750. With the real 0% rate, NOPAT is 200 and
    ROIC is 200/750 = 0.2667. With the buggy fallback to the 20% three-year average it was 0.2133."""
    cur = _li("2025-12-31", ebit=200, tax_expense=0.0, pretax_income=200,
              total_debt=300, shareholders_equity=500, cash_and_equivalents=50)
    prior = [_li("2024-12-31", ebit=180, tax_expense=54, pretax_income=180),
             _li("2023-12-31", ebit=160, tax_expense=48, pretax_income=160)]
    r = compute_all_metrics(_snapshot(cur, prior))["roic"]
    assert abs(r.value - 200 / 750) < 1e-12, f"got {r.value}, expected 0.26667 (the 0% rate was discarded)"
    assert r.inputs_used["tax_rate"] == 0.0
    assert r.status.value == "CALCULATED"
    assert r.note is None


def test_roic_still_falls_back_when_the_current_rate_is_genuinely_unavailable():
    """The fix must not disable the fallback -- only stop it from firing on a real 0.0.
    tax_expense 150 on pretax 100 is a 150% rate, outside [0, 0.5], so effective_tax_rate returns
    None and the 30% three-year average is used."""
    cur = _li("2025-12-31", ebit=200, tax_expense=150, pretax_income=100,
              total_debt=300, shareholders_equity=500, cash_and_equivalents=50)
    prior = [_li("2024-12-31", ebit=180, tax_expense=54, pretax_income=180),
             _li("2023-12-31", ebit=160, tax_expense=48, pretax_income=160)]
    r = compute_all_metrics(_snapshot(cur, prior))["roic"]
    assert abs(r.inputs_used["tax_rate"] - 0.30) < 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)
