"""Arquivo nacional da Distribuição: consulta e recruzamento.

Ao contrário da `/processes`, que é a vista das empresas monitorizadas, isto é o
arquivo cru — tudo o que os 197 tribunais distribuíram, dê ou não match. É o que
permite responder "e no mês passado?" sobre uma empresa que só hoje passou a
interessar, sem depender dos ~180 dias que o CITIUS ainda serve.
"""
from datetime import date
from typing import Annotated, Any

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

from app.auth.deps import CurrentUser, current_user, require_admin
from app.db import get_session
from app.services.court_filings import rematch_all
from app.services.registry_act_parser import normalize_name

router = APIRouter(prefix="/court-filings", tags=["court-filings"])


@router.get("")
async def search_filings(
    session: Annotated[AsyncSession, Depends(get_session)],
    _user: Annotated[CurrentUser, Depends(current_user)],
    q: str | None = Query(default=None, max_length=200),
    tribunal: str | None = Query(default=None, max_length=200),
    especie: str | None = Query(default=None, max_length=200),
    desde: date | None = None,
    ate: date | None = None,
    so_com_acerto: bool = False,
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=50, ge=1, le=200),
) -> dict[str, Any]:
    """Pesquisa no arquivo. `q` procura por nome de parte ou número de processo."""
    where: list[str] = []
    params: dict[str, Any] = {}
    if q:
        # O número de processo tem barras e pontos que o `normalize_name` come;
        # por isso tenta-se primeiro como número e só depois como nome.
        where.append(
            "(f.process_number ILIKE :qlike OR EXISTS ("
            " SELECT 1 FROM court_filing_parties p"
            "  WHERE p.filing_id = f.id AND p.name_norm % :qnorm))"
        )
        params["qlike"] = f"%{q.strip()}%"
        params["qnorm"] = normalize_name(q)
    if tribunal:
        where.append("f.tribunal = :tribunal")
        params["tribunal"] = tribunal
    if especie:
        where.append("f.especie = :especie")
        params["especie"] = especie
    if desde:
        where.append("f.data_distribuicao >= :desde")
        params["desde"] = desde
    if ate:
        where.append("f.data_distribuicao <= :ate")
        params["ate"] = ate
    if so_com_acerto:
        where.append("EXISTS (SELECT 1 FROM court_filing_hits h WHERE h.filing_id = f.id)")
    clause = f"WHERE {' AND '.join(where)}" if where else ""

    total = (
        await session.execute(
            text(f"SELECT count(*) FROM court_filings f {clause}"), params
        )
    ).scalar_one()
    params["lim"] = page_size
    params["off"] = (page - 1) * page_size
    rows = (
        await session.execute(
            text(
                f"""
                SELECT f.id::text, f.process_number, f.tribunal, f.unorganica,
                       f.especie, f.valor, f.data_distribuicao, f.data_entrada,
                       f.observacoes,
                       (SELECT count(*) FROM court_filing_parties p
                         WHERE p.filing_id = f.id) AS n_partes,
                       (SELECT count(*) FROM court_filing_hits h
                         WHERE h.filing_id = f.id) AS n_acertos,
                       (SELECT json_agg(json_build_object(
                                  'name', p.name, 'role', p.role, 'nif', p.nif)
                                ORDER BY p.role NULLS LAST, p.name)
                          FROM court_filing_parties p
                         WHERE p.filing_id = f.id) AS partes
                FROM court_filings f
                {clause}
                ORDER BY f.data_distribuicao DESC NULLS LAST, f.first_seen_at DESC
                LIMIT :lim OFFSET :off
                """
            ),
            params,
        )
    ).mappings().all()
    items = []
    for r in rows:
        item = dict(r)
        item["valor"] = float(item["valor"]) if item["valor"] is not None else None
        item["partes"] = item["partes"] or []
        items.append(item)
    return {"items": items, "total": int(total), "page": page, "page_size": page_size}


@router.get("/facets")
async def filing_facets(
    session: Annotated[AsyncSession, Depends(get_session)],
    _user: Annotated[CurrentUser, Depends(current_user)],
) -> dict[str, Any]:
    """Os valores que a fonte usa, para os filtros não serem adivinhados."""
    tribunais = (
        await session.execute(
            text(
                "SELECT tribunal, count(*) AS n FROM court_filings"
                " GROUP BY 1 ORDER BY 2 DESC LIMIT 250"
            )
        )
    ).all()
    especies = (
        await session.execute(
            text(
                "SELECT especie, count(*) AS n FROM court_filings"
                " WHERE especie IS NOT NULL GROUP BY 1 ORDER BY 2 DESC LIMIT 120"
            )
        )
    ).all()
    resumo = (
        await session.execute(
            text(
                """
                SELECT count(*) AS total,
                       min(data_distribuicao) AS mais_antigo,
                       max(data_distribuicao) AS mais_recente,
                       (SELECT count(*) FROM court_filing_hits) AS acertos
                  FROM court_filings
                """
            )
        )
    ).mappings().first()
    return {
        "tribunais": [{"valor": t, "n": n} for t, n in tribunais],
        "especies": [{"valor": e, "n": n} for e, n in especies],
        "resumo": dict(resumo or {}),
    }


@router.get("/{filing_id}")
async def filing_detail(
    filing_id: str,
    session: Annotated[AsyncSession, Depends(get_session)],
    _user: Annotated[CurrentUser, Depends(current_user)],
) -> dict[str, Any]:
    """Um processo do arquivo, com todas as partes e o que o cruzamento decidiu.

    Os acertos vêm com o `match_kind`: quem foi identificado pelo NIF resolvido
    contra o registo comercial e quem foi só pelo nome. É a diferença entre uma
    certeza e uma semelhança, e quem lê a página tem direito a saber qual é.
    """
    linha = (
        await session.execute(
            text(
                """
                SELECT id::text, process_number, tribunal, unorganica, especie,
                       valor, valor_raw, data_distribuicao, data_entrada,
                       observacoes, first_seen_at, last_seen_at
                  FROM court_filings WHERE id = CAST(:id AS UUID)
                """
            ),
            {"id": filing_id},
        )
    ).mappings().first()
    if not linha:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="não encontrado")

    partes = (
        await session.execute(
            text(
                "SELECT id::text, name, role, nif, nif_source FROM court_filing_parties"
                " WHERE filing_id = CAST(:id AS UUID)"
                " ORDER BY role NULLS LAST, name"
            ),
            {"id": filing_id},
        )
    ).mappings().all()

    acertos = (
        await session.execute(
            text(
                """
                SELECT h.id::text, h.match_kind, h.matched_role, h.matched_nif,
                       c.id::text AS company_id, c.legal_name, c.monitoring_type, c.nif,
                       p.name AS party_name
                  FROM court_filing_hits h
                  LEFT JOIN companies c ON c.id = h.company_id
                  LEFT JOIN court_filing_parties p ON p.id = h.party_id
                 WHERE h.filing_id = CAST(:id AS UUID)
                """
            ),
            {"id": filing_id},
        )
    ).mappings().all()

    item = dict(linha)
    item["valor"] = float(item["valor"]) if item["valor"] is not None else None
    item["partes"] = [dict(p) for p in partes]
    item["acertos"] = [dict(a) for a in acertos]
    return item


@router.post("/rematch")
async def rematch(
    session: Annotated[AsyncSession, Depends(get_session)],
    _admin: Annotated[CurrentUser, Depends(require_admin)],
    desde: date | None = None,
) -> dict[str, int]:
    """Reaplica o cruzamento ao arquivo.

    É o que se corre quando entra uma empresa nova ou quando a regra de
    correspondência muda: sem isto, uma falha de correspondência era silenciosa e
    definitiva, porque a linha tinha sido deitada fora no momento da recolha.
    """
    return await rematch_all(session, since=desde)
