"""SEC filing retry queue and setup-quality gate

Revision ID: 028
Revises: 027
Create Date: 2026-08-03 00:00:00.000000
"""
from datetime import date, datetime, timezone
import json
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


revision: str = "028"
down_revision: Union[str, None] = "027"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    op.create_table(
        "sec_filing_gaps",
        sa.Column("id", sa.Integer(), primary_key=True),
        sa.Column("cik", sa.String(length=10), nullable=False),
        sa.Column("accession", sa.String(length=25), nullable=False),
        sa.Column("form", sa.String(length=12), nullable=True),
        sa.Column("index_date", sa.Date(), nullable=True),
        sa.Column("reason", sa.String(length=64), nullable=False),
        sa.Column("coregistrant_ciks_json", sa.Text(), nullable=True),
        sa.Column("first_seen_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("last_attempted_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("escalated_at", sa.DateTime(timezone=True), nullable=True),
        sa.UniqueConstraint("accession", name="uq_sec_filing_gaps_accession"),
    )
    op.create_index("ix_sec_filing_gaps_cik", "sec_filing_gaps", ["cik"])
    _backfill_retry_queue()


def downgrade() -> None:
    op.drop_index("ix_sec_filing_gaps_cik", table_name="sec_filing_gaps")
    op.drop_table("sec_filing_gaps")


def _as_date(value) -> date | None:
    if isinstance(value, datetime):
        return value.date()
    if isinstance(value, date):
        return value
    if isinstance(value, str):
        try:
            return date.fromisoformat(value)
        except ValueError:
            return None
    return None


def _backfill_retry_queue() -> None:
    """Materialize pre-queue promoted gaps once; runtime never scans history."""
    bind = op.get_bind()
    runs = sa.table(
        "data_import_runs",
        sa.column("source", sa.String()),
        sa.column("status", sa.String()),
        sa.column("validation_json", sa.Text()),
        sa.column("source_max_date", sa.Date()),
        sa.column("started_at", sa.DateTime(timezone=True)),
    )
    snapshots = sa.table(
        "fundamental_snapshots",
        sa.column("cik", sa.String()),
        sa.column("accession", sa.String()),
        sa.column("filed_date", sa.Date()),
    )
    gaps = sa.table(
        "sec_filing_gaps",
        sa.column("cik", sa.String()),
        sa.column("accession", sa.String()),
        sa.column("form", sa.String()),
        sa.column("index_date", sa.Date()),
        sa.column("reason", sa.String()),
        sa.column("coregistrant_ciks_json", sa.Text()),
        sa.column("first_seen_at", sa.DateTime(timezone=True)),
        sa.column("last_attempted_at", sa.DateTime(timezone=True)),
        sa.column("escalated_at", sa.DateTime(timezone=True)),
    )

    snapshot_rows = bind.execute(
        sa.select(snapshots.c.cik, snapshots.c.accession, snapshots.c.filed_date)
    ).all()
    resolved_accessions = {row.accession for row in snapshot_rows}
    latest_filed_by_cik: dict[str, date] = {}
    for row in snapshot_rows:
        if row.filed_date is not None:
            current = latest_filed_by_cik.get(row.cik)
            if current is None or row.filed_date > current:
                latest_filed_by_cik[row.cik] = row.filed_date

    audit_rows = bind.execute(
        sa.select(
            runs.c.validation_json,
            runs.c.source_max_date,
            runs.c.started_at,
        ).where(
            runs.c.source == "sec_facts",
            runs.c.status == "promoted",
            runs.c.validation_json.is_not(None),
        )
    ).all()
    now = datetime.now(timezone.utc)
    candidates: dict[str, dict] = {}
    for audit in audit_rows:
        try:
            summary = json.loads(audit.validation_json)
        except (TypeError, ValueError):
            continue
        if not isinstance(summary, dict):
            continue
        for item in summary.get("missing_xbrl") or []:
            accession = item.get("accession")
            raw_cik = item.get("cik")
            if not accession or raw_cik is None or accession in resolved_accessions:
                continue
            cik = str(raw_cik).zfill(10)
            index_date = _as_date(item.get("index_date")) or _as_date(
                audit.source_max_date
            )
            later_filed = latest_filed_by_cik.get(cik)
            if index_date is not None and later_filed is not None and later_filed > index_date:
                continue
            first_seen = audit.started_at or now
            existing = candidates.get(accession)
            if existing is not None and existing["first_seen_at"] <= first_seen:
                continue
            candidates[accession] = {
                "cik": cik,
                "accession": accession,
                "form": item.get("form"),
                "index_date": index_date,
                "reason": item.get("reason") or "not_in_companyfacts",
                "coregistrant_ciks_json": json.dumps(item.get("coregistrants") or []),
                "first_seen_at": first_seen,
                "last_attempted_at": first_seen,
                "escalated_at": None,
            }
    if candidates:
        op.bulk_insert(gaps, list(candidates.values()))
