"""Actionability gate for incomplete SEC fundamentals."""

from __future__ import annotations

import json
from dataclasses import dataclass

from sqlalchemy import exists, func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.data_import_run import DataImportRun
from app.models.fundamental_snapshot import FundamentalSnapshot
from app.models.sec_filing_gap import SecFilingGap
from app.models.ticker import Ticker

_SEC_FORMS = ("10-K", "10-Q", "10-K/A", "10-Q/A")


@dataclass(frozen=True)
class SetupQuality:
    eligible: bool
    code: str | None = None
    message: str | None = None


async def active_gaps(
    db: AsyncSession,
    ciks: set[str] | None = None,
) -> list[SecFilingGap]:
    """Unresolved gaps that have not been superseded by a later filing."""
    matching_snapshot = exists().where(
        FundamentalSnapshot.accession == SecFilingGap.accession
    )
    gap_date = func.coalesce(
        SecFilingGap.index_date,
        func.date(SecFilingGap.first_seen_at),
    )
    later_snapshot = exists().where(
        FundamentalSnapshot.cik == SecFilingGap.cik,
        FundamentalSnapshot.form.in_(_SEC_FORMS),
        FundamentalSnapshot.filed_date > gap_date,
    )
    stmt = select(SecFilingGap).where(
        ~matching_snapshot,
        ~later_snapshot,
    )
    if ciks is not None:
        if not ciks:
            return []
        stmt = stmt.where(SecFilingGap.cik.in_(ciks))
    return list((await db.execute(stmt)).scalars().all())


async def _latest_validation(db: AsyncSession) -> dict:
    payload = (
        await db.execute(
            select(DataImportRun.validation_json)
            .where(
                DataImportRun.source == "sec_facts",
                DataImportRun.validation_json.is_not(None),
            )
            .order_by(DataImportRun.id.desc())
            .limit(1)
        )
    ).scalar_one_or_none()
    if not payload:
        return {}
    try:
        summary = json.loads(payload)
    except (TypeError, ValueError):
        return {}
    return summary if isinstance(summary, dict) else {}


async def blocked_reasons_by_cik(
    db: AsyncSession,
    ciks: set[str] | None = None,
) -> dict[str, str]:
    """Current SEC blocker code by CIK; no historical audit scan."""
    if ciks is not None and not ciks:
        return {}

    reasons = {
        gap.cik: "sec_filing_gap" for gap in await active_gaps(db, ciks)
    }
    summary = await _latest_validation(db)

    def wanted(cik: str) -> bool:
        return ciks is None or cik in ciks

    # New summaries carry the complete compact CIK set while the detailed lists
    # stay capped for audit readability. Detailed entries supply the reason.
    for cik in summary.get("setup_blocked_ciks") or []:
        normalized = str(cik) if cik else ""
        if normalized and wanted(normalized):
            reasons.setdefault(normalized, "sec_filing_gap")
    for item in summary.get("missing_xbrl") or []:
        normalized = str(item.get("cik") or "")
        if normalized and wanted(normalized):
            reasons.setdefault(normalized, "sec_filing_gap")
    for cik in summary.get("no_xbrl_ciks") or []:
        normalized = str(cik) if cik else ""
        if normalized and wanted(normalized):
            reasons[normalized] = "no_xbrl_filings"
    for item in summary.get("no_xbrl_filings") or []:
        normalized = str(item.get("cik") or "")
        if normalized and wanted(normalized):
            reasons[normalized] = "no_xbrl_filings"
    return reasons


async def blocked_ciks(db: AsyncSession) -> set[str]:
    return set(await blocked_reasons_by_cik(db))


async def blocked_ticker_ids(db: AsyncSession) -> set[int]:
    ciks = await blocked_ciks(db)
    if not ciks:
        return set()
    rows = await db.execute(select(Ticker.id).where(Ticker.cik.in_(ciks)))
    return {int(ticker_id) for ticker_id in rows.scalars()}


async def ticker_quality(db: AsyncSession, symbol: str) -> SetupQuality:
    ticker = (
        await db.execute(
            select(Ticker).where(Ticker.symbol == symbol.strip().upper())
        )
    ).scalar_one_or_none()
    if ticker is None or not ticker.cik:
        return SetupQuality(eligible=True)
    reason = (await blocked_reasons_by_cik(db, {ticker.cik})).get(ticker.cik)
    if reason == "no_xbrl_filings":
        return SetupQuality(
            eligible=False,
            code=reason,
            message=(
                "No SEC 10-K/10-Q is available for this registrant, so new setups "
                "are paused. New registrants clear automatically after their first "
                "filing; a successor shell needs an SEC CIK override."
            ),
        )
    if reason:
        return SetupQuality(
            eligible=False,
            code=reason,
            message=(
                "A recent SEC filing is still being reconciled, so new setups are "
                "paused. The scheduled fundamentals import retries it automatically."
            ),
        )
    return SetupQuality(eligible=True)


async def ticker_is_eligible(db: AsyncSession, ticker_id: int) -> bool:
    cik = (
        await db.execute(select(Ticker.cik).where(Ticker.id == ticker_id))
    ).scalar_one_or_none()
    if not cik:
        return True
    return cik not in await blocked_reasons_by_cik(db, {cik})
