"""Sanity tests for DemoDataAdapter — internal consistency of the synthetic series, and that
every row it produces is traceable back to demo status (spec: never blend demo with production)."""
from __future__ import annotations

from app.adapters.demo import DEMO_SEED_PROFILES, DemoDataAdapter, YEARS_OF_HISTORY
from app.adapters.base import ProviderNotFoundError

adapter = DemoDataAdapter()


def test_unknown_ticker_raises_not_found():
    try:
        adapter.get_company_profile("NOT_A_REAL_DEMO_TICKER")
        raised = False
    except ProviderNotFoundError:
        raised = True
    assert raised


def test_income_statements_have_full_history_and_are_deterministic():
    a = adapter.get_income_statements("AAPL")
    b = adapter.get_income_statements("AAPL")
    assert len(a) == YEARS_OF_HISTORY
    assert [p.line_items["revenue"] for p in a] == [p.line_items["revenue"] for p in b]  # deterministic seed
    # most-recent-first
    assert a[0].period_end > a[-1].period_end


def test_gross_profit_equals_revenue_minus_cogs_when_present():
    periods = adapter.get_income_statements("MSFT")
    for p in periods:
        li = p.line_items
        if li.get("cogs") is not None and li.get("gross_profit") is not None:
            assert abs(li["revenue"] - li["cogs"] - li["gross_profit"]) < 0.2  # independent per-field rounding to 1dp


def test_bank_has_no_cogs_gross_margin_not_meaningful_upstream():
    periods = adapter.get_income_statements("JPM")
    assert all(p.line_items.get("cogs") is None for p in periods)


def test_every_seed_profile_is_reachable_and_profile_matches():
    for seed in DEMO_SEED_PROFILES:
        profile = adapter.get_company_profile(seed.ticker)
        assert profile.ticker == seed.ticker
        assert "DEMO" in (profile.description or "")


def test_list_universe_filters_by_country():
    us_tickers = adapter.list_universe(country_iso2="US")
    assert "AAPL" in us_tickers
    assert "SAP" not in us_tickers


def test_demo_statement_methods_all_return_the_same_merged_line_items():
    """Part A9 evidence. DemoDataAdapter.get_income_statements/get_balance_sheets/get_cash_flows
    all return ONE fully-merged dict per period. That is exactly why the ingestion bug fixed in
    Part A9 -- writing BalanceSheet/CashFlow rows out of the income response -- was invisible in
    demo mode and would have been catastrophic against FMP. Pinned so the demo adapter cannot
    quietly diverge and start hiding the same class of bug again."""
    from app.adapters.demo import DemoDataAdapter

    a = DemoDataAdapter()
    ticker = sorted(a.list_universe())[0]
    inc = {(p.period_end, p.period_type): p.line_items for p in a.get_income_statements(ticker)}
    bal = {(p.period_end, p.period_type): p.line_items for p in a.get_balance_sheets(ticker)}
    cf = {(p.period_end, p.period_type): p.line_items for p in a.get_cash_flows(ticker)}
    assert inc and inc.keys() == bal.keys() == cf.keys()
    for key in inc:
        assert inc[key] == bal[key] == cf[key]


def test_demo_income_response_already_carries_balance_and_cash_flow_fields():
    from app.adapters.demo import DemoDataAdapter

    a = DemoDataAdapter()
    ticker = sorted(a.list_universe())[0]
    li = a.get_income_statements(ticker)[0].line_items
    for field in ("total_assets", "total_debt", "shareholders_equity", "operating_cash_flow",
                  "capital_expenditure", "diluted_shares"):
        assert li.get(field) is not None, f"{field} missing from demo income response"


def test_demo_total_assets_is_populated_for_every_seed_profile():
    """Part A9 finding: the demo generator never emitted total_assets, so ROA, asset turnover, the
    goodwill-concentration flag and the acquisition-driven-growth flag were permanently skipped in
    demo mode -- the mode this platform runs in by default."""
    from app.adapters.demo import DemoDataAdapter

    a = DemoDataAdapter()
    for ticker in a.list_universe():
        for period in a.get_income_statements(ticker):
            assert period.line_items.get("total_assets") is not None, ticker


def test_demo_balance_sheet_balances():
    """total_assets is derived from the liabilities-and-equity side precisely so the synthetic
    balance sheet is internally consistent rather than an independently invented number."""
    from app.adapters.demo import DemoDataAdapter

    a = DemoDataAdapter()
    for ticker in a.list_universe():
        li = a.get_income_statements(ticker)[0].line_items
        implied = (
            li["shareholders_equity"] + li["total_debt"] + li["current_liabilities"]
            + li["revenue"] * 0.05
        )
        assert abs(li["total_assets"] - implied) < max(1.0, abs(implied) * 1e-6), ticker


def test_demo_debt_maturity_split_sums_to_total_debt():
    from app.adapters.demo import DemoDataAdapter

    a = DemoDataAdapter()
    for ticker in a.list_universe():
        li = a.get_income_statements(ticker)[0].line_items
        assert abs((li["short_term_debt"] + li["long_term_debt"]) - li["total_debt"]) < 1.0, ticker


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)
