"""Integration tests for the additive fundamentals API v1 assembly."""

from __future__ import annotations

import os
import tempfile
from datetime import date, datetime, timezone

import pytest
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

from app.database import Base
import app.models  # noqa: F401
from app.models.earnings_event import EarningsEvent
from app.models.fundamental_snapshot import FundamentalSnapshot
from app.models.ohlcv import OHLCVRecord
from app.models.ticker import Ticker
from app.schemas.fundamental import FundamentalResponse
from app.services.fundamentals_api_service import METRIC_KEYS, build_fundamentals_v1

UTC = timezone.utc
TODAY = date(2026, 10, 15)


@pytest.fixture
async def factory():
    fd, path = tempfile.mkstemp(suffix=".db")
    os.close(fd)
    eng = create_async_engine(f"sqlite+aiosqlite:///{path}")
    async with eng.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    try:
        yield async_sessionmaker(eng, class_=AsyncSession, expire_on_commit=False)
    finally:
        await eng.dispose()
        try:
            os.unlink(path)
        except OSError:
            pass


_MONTHS = [3, 6, 9, 12]
_FP = ["Q1", "Q2", "Q3", "FY"]


async def _seed_issuer(s, symbol, cik, sic, rev_base, price, *, eps_base=1.0, snapshots=True):
    t = Ticker(symbol=symbol, cik=cik, sic=sic)
    s.add(t)
    await s.flush()
    if snapshots:
        # three fiscal years so YoY growth reads have a >=3 consecutive run
        for fy, mult in [(2024, 0.9), (2025, 1.0), (2026, 1.1)]:
            shares = {2024: 1050, 2025: 1000, 2026: 950}[fy]  # steady buyback
            rev = [rev_base * mult * x for x in (1.0, 1.05, 1.1, 1.15)]
            eps = [eps_base * mult * x for x in (1.0, 1.05, 1.1, 1.15)]
            for i, fp in enumerate(_FP):
                pe = date(fy, _MONTHS[i], 28)
                s.add(FundamentalSnapshot(
                    cik=cik, accession=f"{cik}-{fy}-{fp}", form="10-K" if fp == "FY" else "10-Q",
                    filed_date=pe, accepted_at=datetime(fy, _MONTHS[i], 28, tzinfo=UTC),
                    period_end=pe, fiscal_year=fy, fiscal_period=fp,
                    revenue=sum(rev[: i + 1]), operating_income=sum(rev[: i + 1]) * 0.2,
                    diluted_eps=sum(eps[: i + 1]), cfo=sum(rev[: i + 1]) * 0.25,
                    capex=sum(rev[: i + 1]) * 0.05, depreciation_amortization=sum(rev[: i + 1]) * 0.05,
                    cash_and_st_investments=40, total_debt=100, shares_outstanding=shares))
    s.add(OHLCVRecord(ticker_id=t.id, date=date(2026, 10, 1), open=price, high=price, low=price, close=price, volume=1000))
    return t.id


async def _seed_group(factory):
    async with factory() as s:
        aapl = await _seed_issuer(s, "AAPL", "0000000001", "3571", rev_base=1000, price=200, eps_base=2.0)
        for i in range(5):  # 5 peers in SIC 35xx so the group has >= 5 valid issuers
            await _seed_issuer(s, f"PEER{i}", f"000000010{i}", "3572", rev_base=500 + i * 100, price=50 + i * 10)
        # AAPL earnings: one upcoming, one past with a surprise
        s.add(EarningsEvent(ticker_id=aapl, announce_date=date(2026, 11, 1), session="amc", source="dolt_earnings"))
        s.add(EarningsEvent(ticker_id=aapl, announce_date=date(2026, 8, 1), session="amc",
                            period_end=date(2026, 6, 30), eps_estimate=2.0, eps_actual=2.2, source="dolt_earnings"))
        await s.commit()
    return aapl


async def test_full_assembly(factory):
    await _seed_group(factory)
    async with factory() as s:
        v1 = await build_fundamentals_v1(s, "AAPL", today=TODAY)

    # earnings
    assert v1["earnings"]["next"] == {"date": "2026-11-01", "session": "amc", "days_until": 17}
    recent = v1["earnings"]["recent"]
    assert recent and recent[0]["surprise_pct"] == pytest.approx(10.0)

    # metrics — fixed key set, all present
    assert [m["key"] for m in v1["metrics"]] == list(METRIC_KEYS)
    by_key = {m["key"]: m for m in v1["metrics"]}
    assert by_key["revenue_growth_yoy"]["value"] is not None
    assert by_key["revenue_growth_yoy"]["source"] == "sec"
    assert len(by_key["operating_margin"]["history"]) >= 3
    # peer industry present for eligible metric (6 issuers), absent for size-dependent net_debt
    assert by_key["operating_margin"]["industry"] is not None
    assert by_key["operating_margin"]["industry"]["peer_count"] == 6
    assert by_key["operating_margin"]["industry"]["label"] == "SIC 35 peers"
    assert by_key["net_debt"]["industry"] is None

    # valuation computed at request time
    val = v1["valuation"]
    assert val["pe"] is not None and val["market_cap_est"] is not None
    assert val["price_date"] == "2026-10-01"
    assert val["pe_industry"] is not None

    # reads: header string + fixed by_key map (every metric + pe + fcf_yield)
    assert v1["reads"]["header"]
    assert set(v1["reads"]["by_key"]) == set(METRIC_KEYS) | {"pe", "fcf_yield"}
    data = FundamentalResponse(symbol="AAPL", pe_ratio=12.3, **v1)  # legacy + v1 additive
    dumped = data.model_dump()
    assert dumped["pe_ratio"] == 12.3  # legacy preserved untouched
    assert dumped["metrics"][0]["key"] == "revenue_growth_yoy"


async def test_same_day_earnings_is_next_with_zero_days(factory):
    async with factory() as s:
        t = Ticker(symbol="TDY", cik=None)
        s.add(t)
        await s.flush()
        s.add(EarningsEvent(ticker_id=t.id, announce_date=TODAY, session="bmo", source="dolt_earnings"))
        s.add(EarningsEvent(ticker_id=t.id, announce_date=date(2026, 9, 1), session="amc",
                            eps_estimate=1.0, eps_actual=1.1, source="dolt_earnings"))
        await s.commit()
    async with factory() as s:
        v1 = await build_fundamentals_v1(s, "TDY", today=TODAY)
    assert v1["earnings"]["next"] == {"date": TODAY.isoformat(), "session": "bmo", "days_until": 0}
    # the same-day event is upcoming, not in recent
    assert all(r["announce_date"] != TODAY.isoformat() for r in v1["earnings"]["recent"])


async def test_eps_growth_read_is_populated(factory):
    await _seed_group(factory)
    async with factory() as s:
        v1 = await build_fundamentals_v1(s, "AAPL", today=TODAY)
    assert v1["reads"]["by_key"]["eps_growth_yoy"] is not None  # EPS read now computed


async def test_non_positive_price_guards_valuation(factory):
    async with factory() as s:
        t = Ticker(symbol="ZERO", cik="0000000055", sic="3571")
        s.add(t)
        await s.flush()
        s.add(FundamentalSnapshot(cik="0000000055", accession="z", form="10-K", filed_date=date(2026, 1, 1),
                                  accepted_at=datetime(2026, 1, 1, tzinfo=UTC), period_end=date(2025, 12, 31),
                                  fiscal_year=2025, fiscal_period="FY", diluted_eps=5.0, shares_outstanding=1000))
        s.add(OHLCVRecord(ticker_id=t.id, date=date(2026, 1, 2), open=0, high=0, low=0, close=0, volume=1))
        await s.commit()
    async with factory() as s:
        v1 = await build_fundamentals_v1(s, "ZERO", today=TODAY)
    assert v1["valuation"] is None  # close of 0 is not a usable price


async def test_no_cik_ticker_yields_null_metrics(factory):
    async with factory() as s:
        s.add(Ticker(symbol="ADR", cik=None))  # no SEC identity
        await s.commit()
    async with factory() as s:
        v1 = await build_fundamentals_v1(s, "ADR", today=TODAY)
    assert v1["valuation"] is None
    assert all(m["value"] is None and m["industry"] is None for m in v1["metrics"])
    assert v1["reads"]["header"] is None
    assert v1["reads"]["by_key"] == {k: None for k in list(METRIC_KEYS) + ["pe", "fcf_yield"]}


async def test_industry_omitted_below_five_peers(factory):
    async with factory() as s:
        await _seed_issuer(s, "SOLO", "0000000009", "9999", rev_base=1000, price=100, eps_base=2.0)
        await s.commit()
    async with factory() as s:
        v1 = await build_fundamentals_v1(s, "SOLO", today=TODAY)
    # only 1 issuer in the group -> below MIN_PEERS -> every industry omitted
    assert all(m["industry"] is None for m in v1["metrics"])
    assert v1["valuation"]["pe_industry"] is None
    # but the subject's own valuation still computes
    assert v1["valuation"]["pe"] is not None


async def test_valuation_guarded_without_price(factory):
    async with factory() as s:
        t = Ticker(symbol="NOPX", cik="0000000077", sic="3571")
        s.add(t)
        await s.flush()
        # snapshots but NO ohlcv close
        s.add(FundamentalSnapshot(cik="0000000077", accession="a", form="10-K", filed_date=date(2026, 1, 1),
                                  accepted_at=datetime(2026, 1, 1, tzinfo=UTC), period_end=date(2025, 12, 31),
                                  fiscal_year=2025, fiscal_period="FY", diluted_eps=5.0, shares_outstanding=1000))
        await s.commit()
    async with factory() as s:
        v1 = await build_fundamentals_v1(s, "NOPX", today=TODAY)
    # no usable price -> valuation is null under the approved contract
    assert v1["valuation"] is None


async def test_multiclass_subject_priced_by_requested_ticker(factory):
    cik = "0001652044"
    async with factory() as s:
        # GOOGL and GOOG share one CIK/snapshots but trade at different prices
        await _seed_issuer(s, "GOOGL", cik, "7372", rev_base=1000, price=200, eps_base=2.0)
        # add a second class sharing the CIK: same snapshots exist; just its own ticker+price
        goog = Ticker(symbol="GOOG", cik=cik, sic="7372")
        s.add(goog)
        await s.flush()
        s.add(OHLCVRecord(ticker_id=goog.id, date=date(2026, 10, 1), open=100, high=100, low=100, close=100, volume=1))
        for i in range(4):  # peers so the group has >= 5 valid issuers
            await _seed_issuer(s, f"P{i}", f"000000020{i}", "7373", rev_base=600 + i * 50, price=40 + i * 5)
        await s.commit()

    async with factory() as s:
        googl = await build_fundamentals_v1(s, "GOOGL", today=TODAY)
        goog_v = await build_fundamentals_v1(s, "GOOG", today=TODAY)

    # subject P/E uses the REQUESTED class's price (200 vs 100), not an arbitrary sibling
    assert googl["valuation"]["pe"] == pytest.approx(goog_v["valuation"]["pe"] * 2, rel=1e-6)


async def test_endpoint_merges_legacy_and_v1(client, db_session):
    from datetime import timezone as _tz

    from app.dependencies import require_access
    from app.main import app
    from app.models.fundamental import FundamentalData

    app.dependency_overrides[require_access] = lambda: None
    try:
        t = Ticker(symbol="AAPL", cik="0000000001", sic="3571")
        db_session.add(t)
        await db_session.flush()
        db_session.add(FundamentalData(ticker_id=t.id, pe_ratio=12.3, revenue_growth=5.0,
                                       fetched_at=datetime(2026, 1, 1, tzinfo=_tz.utc)))
        db_session.add(FundamentalSnapshot(cik="0000000001", accession="a", form="10-K",
                                           filed_date=date(2026, 1, 1), accepted_at=datetime(2026, 1, 1, tzinfo=_tz.utc),
                                           period_end=date(2025, 12, 31), fiscal_year=2025, fiscal_period="FY",
                                           diluted_eps=5.0, shares_outstanding=1000))
        db_session.add(OHLCVRecord(ticker_id=t.id, date=date(2026, 1, 2), open=100, high=100, low=100, close=100, volume=1))
        await db_session.flush()

        resp = await client.get("/api/v1/fundamentals/AAPL")
        assert resp.status_code == 200
        data = resp.json()["data"]
        assert data["pe_ratio"] == 12.3            # legacy preserved
        assert data["revenue_growth"] == 5.0
        assert len(data["metrics"]) == 7           # additive v1
        assert data["earnings"] is not None
        assert "by_key" in data["reads"]
        assert data["valuation"]["price_date"] == "2026-01-02"
    finally:
        app.dependency_overrides.pop(require_access, None)
