"""A5 activation: local candidate derivation and compat-cache refresh."""

from __future__ import annotations

import json
from datetime import date, datetime, timedelta, timezone

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

from app.database import Base
from app.models.earnings_event import EarningsEvent
from app.models.fundamental import FundamentalData
from app.models.fundamental_snapshot import FundamentalSnapshot
from app.models.ohlcv import OHLCVRecord
from app.models.score import CompositeScore, DimensionScore
from app.models.ticker import Ticker
from app.services import fundamentals_candidate_service as candidates
from app.services import fundamentals_derivation as deriv
from app.services import fundamental_data_refresh_service as refresh_service


UTC = timezone.utc
NOW = datetime(2026, 7, 24, 10, 0, tzinfo=UTC)
TODAY = date(2026, 7, 24)

_engine = create_async_engine("sqlite+aiosqlite://", echo=False)
_session_factory = async_sessionmaker(
    _engine, class_=AsyncSession, expire_on_commit=False
)


@pytest.fixture(autouse=True)
async def _setup_tables():
    async with _engine.begin() as connection:
        await connection.run_sync(Base.metadata.create_all)
    yield
    async with _engine.begin() as connection:
        await connection.run_sync(Base.metadata.drop_all)


@pytest.fixture
async def session() -> AsyncSession:
    async with _session_factory() as db:
        yield db


def _snapshot_rows(cik: str) -> list[FundamentalSnapshot]:
    rows: list[FundamentalSnapshot] = []
    periods = ("Q1", "Q2", "Q3", "FY")
    months = (3, 6, 9, 12)
    for fiscal_year, multiplier in ((2025, 1.0), (2026, 1.1)):
        revenue = [100 * multiplier, 110 * multiplier, 120 * multiplier, 130 * multiplier]
        eps = [1.0 * multiplier, 1.1 * multiplier, 1.2 * multiplier, 1.3 * multiplier]
        for index, fiscal_period in enumerate(periods):
            period_end = date(fiscal_year, months[index], 28)
            rows.append(
                FundamentalSnapshot(
                    cik=cik,
                    accession=f"{cik}-{fiscal_year}-{fiscal_period}",
                    form="10-K" if fiscal_period == "FY" else "10-Q",
                    filed_date=period_end,
                    accepted_at=datetime(
                        fiscal_year, months[index], 28, tzinfo=UTC
                    ),
                    period_end=period_end,
                    fiscal_year=fiscal_year,
                    fiscal_period=fiscal_period,
                    revenue=sum(revenue[: index + 1]),
                    diluted_eps=sum(eps[: index + 1]),
                    shares_outstanding=1_000,
                )
            )
    return rows


async def test_refresh_updates_all_fields_and_invalidates_scores(
    session: AsyncSession,
):
    first = Ticker(symbol="AAA", cik="0000000001")
    second = Ticker(symbol="AAB", cik="0000000001")
    session.add_all([first, second])
    await session.flush()
    session.add_all(_snapshot_rows(first.cik))
    session.add_all(
        [
            OHLCVRecord(
                ticker_id=first.id,
                date=TODAY - timedelta(days=1),
                open=100,
                high=100,
                low=100,
                close=100,
                volume=100,
            ),
            OHLCVRecord(
                ticker_id=second.id,
                date=TODAY - timedelta(days=1),
                open=200,
                high=200,
                low=200,
                close=200,
                volume=100,
            ),
            EarningsEvent(
                ticker_id=first.id,
                announce_date=TODAY - timedelta(days=10),
                session="amc",
                eps_estimate=2,
                eps_actual=2.2,
                source="dolt_earnings",
            ),
            EarningsEvent(
                ticker_id=first.id,
                announce_date=TODAY,
                session="amc",
                source="dolt_earnings",
            ),
        ]
    )
    for ticker in (first, second):
        session.add(
            FundamentalData(
                ticker_id=ticker.id,
                pe_ratio=1,
                revenue_growth=1,
                earnings_surprise=1,
                market_cap=1,
                fetched_at=NOW - timedelta(days=1),
            )
        )
        session.add(
            DimensionScore(
                ticker_id=ticker.id,
                dimension="fundamental",
                score=50,
                is_stale=False,
                computed_at=NOW,
            )
        )
        session.add(
            CompositeScore(
                ticker_id=ticker.id,
                score=50,
                is_stale=False,
                weights_json="{}",
                computed_at=NOW,
            )
        )
    await session.commit()

    summary = await refresh_service.refresh(
        session, now=NOW, today=TODAY
    )

    stored = {
        row.ticker_id: row
        for row in (
            await session.execute(select(FundamentalData))
        ).scalars()
    }
    assert summary["refreshed"] == 2
    assert summary["score_inputs_changed"] == 2
    assert stored[first.id].pe_ratio == pytest.approx(100 / 5.06)
    assert stored[second.id].pe_ratio == pytest.approx(200 / 5.06)
    assert stored[first.id].revenue_growth == pytest.approx(10)
    assert stored[first.id].earnings_surprise == pytest.approx(10)
    assert stored[first.id].market_cap == pytest.approx(100_000)
    assert stored[first.id].next_earnings_date == TODAY
    metadata = json.loads(stored[first.id].unavailable_fields_json)
    assert metadata["source_pe_ratio"] == "sec_facts+ohlcv_records"
    assert metadata["source_next_earnings_date"] == "dolt_earnings"

    dimensions = (
        await session.execute(select(DimensionScore))
    ).scalars().all()
    composites = (
        await session.execute(select(CompositeScore))
    ).scalars().all()
    assert all(row.is_stale for row in dimensions)
    assert all(row.is_stale for row in composites)

    for row in (*dimensions, *composites):
        row.is_stale = False
    await session.commit()
    unchanged = await refresh_service.refresh(
        session, now=NOW + timedelta(hours=1), today=TODAY
    )
    assert unchanged["score_inputs_changed"] == 0
    assert not any(
        (await session.execute(select(DimensionScore.is_stale))).scalars()
    )
    assert not any(
        (await session.execute(select(CompositeScore.is_stale))).scalars()
    )


async def test_candidate_uses_guarded_derive_outputs_and_share_fallback(
    session: AsyncSession, monkeypatch
):
    ticker = Ticker(symbol="GUARD", cik="0000000002")
    session.add(ticker)
    await session.flush()
    session.add(
        FundamentalSnapshot(
            cik=ticker.cik,
            accession="raw-accession",
            form="10-Q",
            filed_date=TODAY,
            accepted_at=NOW,
            period_end=TODAY,
            fiscal_year=2026,
            fiscal_period="Q2",
            diluted_eps=99,
            shares_outstanding=999,
        )
    )
    session.add(
        OHLCVRecord(
            ticker_id=ticker.id,
            date=TODAY,
            open=50,
            high=50,
            low=50,
            close=50,
            volume=100,
        )
    )
    await session.commit()

    def guarded(_rows):
        return deriv.DerivedFundamentals(
            metrics={
                "revenue_growth_yoy": deriv.MetricSeries(value=7)
            },
            ttm_diluted_eps=None,
            ttm_diluted_eps_caveat="split guard applied",
            shares_outstanding=123,
            shares_outstanding_estimated=True,
            latest_period_end=TODAY,
            latest_filed_date=TODAY,
        )

    monkeypatch.setattr(candidates.deriv, "derive", guarded)
    candidate = (await candidates.build_candidates(session, today=TODAY))[0]

    assert candidate.pe_ratio is None
    assert candidate.market_cap == 50 * 123
    assert candidate.revenue_growth == 7
    assert candidate.unavailable_fields["pe_ratio"] == "split guard applied"
    assert "weighted-average" in candidate.unavailable_fields["market_cap_estimated"]
