"""Cruzamento dos anúncios CIRE contra a watchlist e as empresas monitorizadas.

O match é **só por NIF**. Há a tentação de o fazer também por nome com trigram,
como o `search.py` faz, mas aqui o custo de um falso positivo é alto: alguém
avisado de que um cliente entrou em insolvência vai agir com base nisso. Nomes de
empresas portuguesas repetem-se muito ("Silva & Filhos, Lda"), e o anúncio traz
sempre o NIF das partes relevantes. Preferimos falhar um match a inventar um.
"""
import logging
from typing import Any

from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger(__name__)

# Papéis que interessam num anúncio de insolvência. Quando a entidade vigiada é
# a INSOLVENTE, há um prazo para lhe reclamar créditos — é o caso que motiva
# tudo isto. Quando aparece como credora, é informativo: já está no processo.
DEBTOR_ROLES = ("insolvente", "devedor", "requerido")


async def match_announcement(session: AsyncSession, announcement_id: str) -> int:
    """Cria os hits para um anúncio acabado de inserir. Devolve quantos criou.

    Cruza as partes contra `insolvency_watchlist` (clientes vindos do Sabichão)
    e `companies` (concorrentes, internas, relacionadas). Não envia nada — o
    envio é feito depois, a partir dos hits por notificar, para o sweep não ficar
    dependente do SMTP.
    """
    rows = (
        await session.execute(
            text(
                """
                INSERT INTO insolvency_hits (
                    announcement_id, watchlist_id, company_id,
                    matched_nif, matched_name, matched_role, claim_deadline
                )
                SELECT
                    a.id,
                    w.id,
                    c.id,
                    p.nif,
                    COALESCE(w.name, c.legal_name, p.name),
                    p.role,
                    a.claim_deadline
                FROM cire_announcement_parties p
                JOIN cire_announcements a ON a.id = p.announcement_id
                LEFT JOIN insolvency_watchlist w
                       ON w.nif = p.nif AND w.active
                LEFT JOIN companies c
                       ON c.nif = p.nif
                WHERE p.announcement_id = :aid
                  AND p.nif IS NOT NULL
                  AND (w.id IS NOT NULL OR c.id IS NOT NULL)
                ON CONFLICT (announcement_id, matched_nif) DO NOTHING
                RETURNING id::text
                """
            ),
            {"aid": announcement_id},
        )
    ).all()
    if rows:
        logger.info("anúncio %s: %d hit(s) na watchlist/empresas", announcement_id, len(rows))
    return len(rows)


async def rematch_nif(session: AsyncSession, nif: str) -> int:
    """Cruza um NIF novo contra **todo** o histórico de anúncios já guardado.

    É isto que dá sentido a guardar os anúncios nacionais: quando um cliente é
    adicionado à watchlist hoje, queremos saber se ele já foi declarado
    insolvente na semana passada, não só de hoje em diante.
    """
    rows = (
        await session.execute(
            text(
                """
                INSERT INTO insolvency_hits (
                    announcement_id, watchlist_id, company_id,
                    matched_nif, matched_name, matched_role, claim_deadline
                )
                SELECT
                    a.id, w.id, c.id, p.nif,
                    COALESCE(w.name, c.legal_name, p.name), p.role, a.claim_deadline
                FROM cire_announcement_parties p
                JOIN cire_announcements a ON a.id = p.announcement_id
                LEFT JOIN insolvency_watchlist w ON w.nif = p.nif AND w.active
                LEFT JOIN companies c ON c.nif = p.nif
                WHERE p.nif = :nif
                  AND (w.id IS NOT NULL OR c.id IS NOT NULL)
                ON CONFLICT (announcement_id, matched_nif) DO NOTHING
                RETURNING id::text
                """
            ),
            {"nif": nif},
        )
    ).all()
    return len(rows)


async def pending_notifications(session: AsyncSession, limit: int = 100) -> list[dict[str, Any]]:
    """Hits por notificar em que a entidade vigiada é a insolvente e há prazo
    **ainda por cumprir**.

    Dois filtros, por razões diferentes:

    * **Só o papel de insolvente.** É onde existe uma acção com prazo a perder;
      aparecer como credor num processo alheio não abre prazo nenhum.
    * **Só prazos que ainda não expiraram.** Um backfill histórico encontra
      insolvências antigas cujo prazo já passou; enviar "reclamar créditos até"
      uma data do mês passado é ruído, e em lote seria alarmante. Continuam
      visíveis em `/insolvencies`, marcados como expirados — a informação não
      se perde, só não vira email.
    * **Só entidades que podem gerar aviso.** A cadeia está no
      `app_settings.notifiable_nifs_sql`: interruptor geral, excepção por empresa
      e defeito do tipo. Um hit que exista pela watchlist (é um cliente) alerta
      mesmo que a mesma entidade também esteja marcada como análise — quem pôs o
      NIF na watchlist quer ser avisado.
    * **Só clientes.** É a Segunor que factura clientes, portanto é a insolvência
      de um cliente que lhe abre prazo para reclamar créditos. A de um concorrente
      é notícia de mercado, e um email a dizer "reclamar créditos até" sobre um
      concorrente afirmaria uma coisa falsa. Sem este filtro o despacho aceitava-a:
      a matriz de notificações tem `competitor` ligado, e com razão — só que para
      outra coisa.
    """
    from app.services.app_settings import emails_enabled, notifiable_nifs_sql

    if not await emails_enabled(session):
        return []
    notifiable = await notifiable_nifs_sql()
    rows = (
        await session.execute(
            text(
                f"""
                SELECT h.id::text, h.matched_nif, h.matched_name, h.matched_role,
                       h.claim_deadline, a.id::text AS announcement_id,
                       a.process_number, a.tribunal, a.ato, a.pub_date,
                       a.claim_days, a.claim_days_source, a.claim_deadline_estimated,
                       a.admin_name, a.admin_email
                FROM insolvency_hits h
                JOIN cire_announcements a ON a.id = h.announcement_id
                WHERE h.notified_at IS NULL
                  AND a.opens_claim_window
                  AND lower(COALESCE(h.matched_role,'')) = ANY(:roles)
                  AND (h.claim_deadline IS NULL OR h.claim_deadline >= CURRENT_DATE)
                  AND h.matched_nif IN ({notifiable})
                  AND (
                    h.watchlist_id IS NOT NULL
                    OR EXISTS (
                        SELECT 1 FROM companies c
                         WHERE c.id = h.company_id AND c.monitoring_type = 'client'
                    )
                  )
                ORDER BY h.claim_deadline NULLS LAST, h.created_at
                LIMIT :lim
                """
            ),
            {"roles": list(DEBTOR_ROLES), "lim": limit},
        )
    ).mappings().all()
    return [dict(r) for r in rows]


async def mark_notified(session: AsyncSession, hit_id: str) -> None:
    await session.execute(
        text("UPDATE insolvency_hits SET notified_at = now() WHERE id = :id"),
        {"id": hit_id},
    )
