"""
Tests the FMP adapter's request/response parsing against a mocked transport (httpx.MockTransport)
— no real network call is made, but the URL construction, auth-error handling, and field-mapping
logic run for real. This is what's actually verifiable in this build environment; the mapping's
fidelity to FMP's *live* response shape still needs to be checked with a real key
(see app/adapters/fmp.py module docstring and TROUBLESHOOTING.md).
"""
from __future__ import annotations

import httpx

from app.adapters.base import ProviderAuthError, ProviderNotFoundError
from app.adapters.fmp import FMPAdapter

FAKE_PROFILE_RESPONSE = [{
    "companyName": "Apple Inc.", "exchangeShortName": "NASDAQ", "country": "US",
    "sector": "Technology", "industry": "Consumer Electronics", "currency": "USD",
    "isin": "US0378331005", "website": "https://apple.com", "description": "Makes phones.",
    "beta": 1.25,
}]

FAKE_INCOME_RESPONSE = [{
    "date": "2025-09-30", "period": "FY", "fillingDate": "2025-11-01",
    "reportedCurrency": "USD", "revenue": 400000000000, "costOfRevenue": 220000000000,
    "grossProfit": 180000000000, "operatingIncome": 120000000000, "ebitda": 135000000000,
    "netIncome": 100000000000, "eps": 6.5, "epsdiluted": 6.4, "incomeTaxExpense": 20000000000,
    "incomeBeforeTax": 120000000000, "interestExpense": 3000000000,
}]


def _mock_transport(handler):
    return httpx.MockTransport(handler)


def test_get_company_profile_parses_known_fields():
    def handler(request: httpx.Request) -> httpx.Response:
        assert request.url.path.endswith("/profile/AAPL")
        assert "apikey" in request.url.params
        return httpx.Response(200, json=FAKE_PROFILE_RESPONSE)

    adapter = FMPAdapter(api_key="fake", transport=_mock_transport(handler))
    profile = adapter.get_company_profile("AAPL")
    assert profile.legal_name == "Apple Inc."
    assert profile.country_iso2 == "US"
    assert profile.beta == 1.25


def test_get_company_profile_not_found_raises():
    def handler(request: httpx.Request) -> httpx.Response:
        return httpx.Response(200, json=[])

    adapter = FMPAdapter(api_key="fake", transport=_mock_transport(handler))
    try:
        adapter.get_company_profile("NOPE")
        raised = False
    except ProviderNotFoundError:
        raised = True
    assert raised


def test_auth_error_raised_on_401():
    def handler(request: httpx.Request) -> httpx.Response:
        return httpx.Response(401, text="unauthorized")

    adapter = FMPAdapter(api_key="bad-key", transport=_mock_transport(handler))
    try:
        adapter.get_company_profile("AAPL")
        raised = False
    except ProviderAuthError:
        raised = True
    assert raised


def test_income_statement_field_mapping_and_capex_sign_normalization():
    def handler(request: httpx.Request) -> httpx.Response:
        assert request.url.path.endswith("/income-statement/AAPL")
        return httpx.Response(200, json=FAKE_INCOME_RESPONSE)

    adapter = FMPAdapter(api_key="fake", transport=_mock_transport(handler))
    periods = adapter.get_income_statements("AAPL", period="annual", limit=1)
    assert len(periods) == 1
    li = periods[0].line_items
    assert li["revenue"] == 400000000000
    assert li["gross_profit"] == 180000000000
    assert li["eps_diluted"] == 6.4
    assert periods[0].period_end.isoformat() == "2025-09-30"
    assert periods[0].filing_date.isoformat() == "2025-11-01"


def test_capex_and_buyback_sign_normalized_to_positive_magnitude():
    from app.adapters.fmp import _map_fields, _CASHFLOW_MAP
    raw = {"capitalExpenditure": -50.0, "commonStockRepurchased": -20.0, "dividendsPaid": -10.0, "operatingCashFlow": 100.0}
    mapped = _map_fields(raw, _CASHFLOW_MAP)
    assert mapped["capital_expenditure"] == 50.0
    assert mapped["buybacks"] == 20.0
    assert mapped["dividends_paid"] == 10.0
    assert mapped["operating_cash_flow"] == 100.0  # not in the sign-flip list, untouched


# --- Part A9: evidence for the critical ingestion bug this pass fixed ---

FAKE_BALANCE_RESPONSE = [{
    "date": "2025-09-30", "period": "FY", "fillingDate": "2025-11-01",
    "reportedCurrency": "USD", "totalAssets": 350000000000, "totalDebt": 110000000000,
    "totalStockholdersEquity": 60000000000, "totalCurrentAssets": 140000000000,
    "totalCurrentLiabilities": 130000000000, "cashAndCashEquivalents": 30000000000,
    "goodwill": 0, "intangibleAssets": 0, "netReceivables": 60000000000, "inventory": 7000000000,
}]

FAKE_CASHFLOW_RESPONSE = [{
    "date": "2025-09-30", "period": "FY", "fillingDate": "2025-11-01",
    "reportedCurrency": "USD", "operatingCashFlow": 118000000000,
    "capitalExpenditure": -11000000000, "freeCashFlow": 107000000000,
    "dividendsPaid": -15000000000, "commonStockRepurchased": -85000000000,
    "commonStockIssued": 1000000000, "netIncome": 100000000000,
}]


def _statement_adapter():
    """One adapter whose mock transport serves all three statement endpoints."""
    def handler(request: httpx.Request) -> httpx.Response:
        path = request.url.path
        if "income-statement" in path:
            return httpx.Response(200, json=FAKE_INCOME_RESPONSE)
        if "balance-sheet-statement" in path:
            return httpx.Response(200, json=FAKE_BALANCE_RESPONSE)
        if "cash-flow-statement" in path:
            return httpx.Response(200, json=FAKE_CASHFLOW_RESPONSE)
        raise AssertionError(f"unexpected path {path}")
    return FMPAdapter(api_key="k", transport=httpx.MockTransport(handler))


def test_fmp_income_response_carries_no_balance_or_cash_flow_fields():
    """This is the root cause of the Part A9 ingestion bug, pinned as a fact about FMP rather than
    left as an assertion in a document. `ingest_security()` used to write the BalanceSheet,
    CashFlow and Shares rows out of THIS dict -- which contains none of their fields, so every one
    of those columns would have been NULL for real FMP data."""
    li = _statement_adapter().get_income_statements("AAPL")[0].line_items
    for absent in ("total_assets", "total_debt", "shareholders_equity", "operating_cash_flow",
                   "capital_expenditure", "diluted_shares"):
        assert li.get(absent) is None, f"{absent} unexpectedly present in FMP income response"
    assert li["revenue"] == 400000000000  # ...while the income fields are there as expected


def test_fmp_balance_and_cash_flow_endpoints_do_carry_those_fields():
    a = _statement_adapter()
    bal = a.get_balance_sheets("AAPL")[0].line_items
    cf = a.get_cash_flows("AAPL")[0].line_items
    assert bal["total_assets"] == 350000000000
    assert bal["shareholders_equity"] == 60000000000
    assert cf["operating_cash_flow"] == 118000000000
    assert cf["capital_expenditure"] == 11000000000  # sign-normalised to a positive magnitude


def test_merging_the_three_fmp_statements_produces_a_complete_period():
    """End-to-end evidence that the Part A9 fix works on real adapter output (mocked transport,
    real parsing code): merge_statement_line_items over the three FMP responses yields every field
    the four statement tables need."""
    from app.adapters.resolver import merge_statement_line_items

    a = _statement_adapter()
    merged = merge_statement_line_items(
        a.get_income_statements("AAPL")[0].line_items,
        a.get_balance_sheets("AAPL")[0].line_items,
        a.get_cash_flows("AAPL")[0].line_items,
    )
    assert merged["revenue"] == 400000000000
    assert merged["total_assets"] == 350000000000
    assert merged["operating_cash_flow"] == 118000000000
    assert merged["capital_expenditure"] == 11000000000
    # income statement stays authoritative for the field both endpoints report
    assert merged["net_income"] == 100000000000


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)
