"""Dossiê consolidado de uma entidade, por NIF (v1 public API).

Alimenta a tab "Intel" da ficha de cliente do Sabichão. A ideia é que o Intel
Grid continue a ser a plataforma de monitorização, mas que a informação apareça
onde o cliente é gerido — quem atende um cliente não vai a outro portal.

É composição de dados que já existem (empresa, processos, insolvências,
publicações, contratos), não lógica nova: uma única chamada em vez de cinco,
porque a tab carrega em lazy-load e não pode ser lenta.
"""
from typing import Annotated, Any

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

from app.auth.api_key_auth import require_api_key
from app.db import get_session
from app.rate_limit import limiter

router = APIRouter(prefix="/dossier", tags=["public-api"])

MAX_ITEMS = 20


def _norm_nif(raw: str | None) -> str | None:
    if not raw:
        return None
    digits = "".join(ch for ch in raw if ch.isdigit())
    return digits if len(digits) == 9 else None


@router.get("")
@limiter.limit("120/minute")
async def dossier(
    request: Request,
    session: Annotated[AsyncSession, Depends(get_session)],
    _key: Annotated[dict[str, Any], Depends(require_api_key)],
    nif: str = Query(description="NIF/NIPC; aceita espaços e pontuação"),
) -> dict[str, Any]:
    nif_norm = _norm_nif(nif)
    if not nif_norm:
        raise HTTPException(status_code=400, detail="NIF inválido")

    company = (
        await session.execute(
            text(
                """
                SELECT c.id::text, c.nif, c.legal_name, c.cae, c.address,
                       c.natureza_juridica, c.distrito, c.concelho, c.freguesia,
                       c.codigo_postal, c.localidade, c.objeto_social,
                       c.capital_social, c.registry_complete, c.risk_score,
                       c.monitoring_type, c.active,
                       (SELECT ac.alvara_number FROM alvara_companies ac
                         WHERE ac.nipc = c.nif AND ac.alvara_type = 'A'
                           AND ac.removed_at IS NULL LIMIT 1) AS alvara
                FROM companies c WHERE c.nif = :nif
                """
            ),
            {"nif": nif_norm},
        )
    ).mappings().first()

    # Prazos de reclamação: o que tem relógio a contar vem primeiro, porque é
    # a única parte deste dossiê que se perde se ninguém agir.
    deadlines = (
        await session.execute(
            text(
                """
                SELECT h.claim_deadline, (h.claim_deadline - CURRENT_DATE) AS days_left,
                       h.matched_role, h.acknowledged_at,
                       a.process_number, a.tribunal, a.ato, a.pub_date,
                       a.claim_days, 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.matched_nif = :nif AND a.opens_claim_window
                ORDER BY h.claim_deadline NULLS LAST
                LIMIT :lim
                """
            ),
            {"nif": nif_norm, "lim": MAX_ITEMS},
        )
    ).mappings().all()

    announcements = (
        await session.execute(
            text(
                """
                SELECT DISTINCT a.id::text, a.pub_date, a.ato, a.process_number,
                       a.tribunal, p.role
                FROM cire_announcement_parties p
                JOIN cire_announcements a ON a.id = p.announcement_id
                WHERE p.nif = :nif
                ORDER BY a.pub_date DESC NULLS LAST
                LIMIT :lim
                """
            ),
            {"nif": nif_norm, "lim": MAX_ITEMS},
        )
    ).mappings().all()

    processes: list[dict[str, Any]] = []
    publications: list[dict[str, Any]] = []
    people: list[dict[str, Any]] = []
    contracts: dict[str, Any] = {}
    counts: dict[str, Any] = {}

    if company:
        cid = company["id"]
        processes = [
            dict(r)
            for r in (
                await session.execute(
                    text(
                        """
                        SELECT process_number, tribunal, juizo, species, source,
                               role_in_process, date_filed
                        FROM processes WHERE company_id = :cid
                        ORDER BY date_filed DESC LIMIT :lim
                        """
                    ),
                    {"cid": cid, "lim": MAX_ITEMS},
                )
            ).mappings().all()
        ]
        publications = [
            dict(r)
            for r in (
                await session.execute(
                    text(
                        """
                        SELECT title, date, type, source
                        FROM dre_publications WHERE company_id = :cid
                        ORDER BY date DESC NULLS LAST LIMIT :lim
                        """
                    ),
                    {"cid": cid, "lim": MAX_ITEMS},
                )
            ).mappings().all()
        ]
        # Vinha do `publication_people`, que não tem noção de estado actual e
        # portanto mostrava para sempre a última publicação onde a pessoa
        # aparecia. Na SEGUNOR isso punha uma sócia de 2019 ao lado da cap table
        # de 2026, onde ela já não consta — duas listas a dizer coisas
        # diferentes sobre a mesma empresa, na mesma página.
        people = [
            dict(r)
            for r in (
                await session.execute(
                    text(
                        """
                        SELECT h.name AS person_name, h.nif AS person_nif,
                               COALESCE(r.role_label,
                                        CASE WHEN r.edge_type = 'holds_quota'
                                             THEN 'sócio' ELSE r.edge_type END) AS cargo,
                               max(a.act_date) AS act_date,
                               sum(r.quota_amount) AS quota_amount,
                               sum(r.quota_share) AS quota_share,
                               count(*) FILTER (
                                   WHERE r.edge_type = 'holds_quota') AS quotas
                          FROM registry_edges r
                          JOIN registry_entities h ON h.id = r.holder_id
                          JOIN registry_entities s ON s.id = r.subject_id
                          JOIN registry_acts a ON a.id = r.source_act_id
                         WHERE s.nif = :nif AND r.is_current
                           AND r.edge_type <> 'spouse_of'
                         GROUP BY h.id, h.name, h.nif, r.edge_type, r.role_label
                         ORDER BY r.edge_type, sum(r.quota_amount) DESC NULLS LAST
                         LIMIT :lim
                        """
                    ),
                    {"nif": nif_norm, "lim": MAX_ITEMS},
                )
            ).mappings().all()
        ]
        row = (
            await session.execute(
                text(
                    """
                    SELECT count(*) AS total,
                           COALESCE(sum(COALESCE(actual_price, contract_price, base_price)), 0)
                             AS valor
                    FROM public_contracts WHERE company_id = :cid
                    """
                ),
                {"cid": cid},
            )
        ).mappings().first()
        contracts = dict(row) if row else {}
        row = (
            await session.execute(
                text(
                    """
                    SELECT count(*) FILTER (WHERE source='cire') AS cire,
                           count(*) FILTER (WHERE source='distribuicao') AS distribuicao,
                           count(*) AS total
                    FROM processes WHERE company_id = :cid
                    """
                ),
                {"cid": cid},
            )
        ).mappings().first()
        counts = dict(row) if row else {}

    # Rede societária: sócios, gerência e participações, do registo comercial.
    # Funciona para NIFs que não estão em `companies` — é o ponto: um cliente que
    # seja pessoa singular não tem ficha de empresa, mas tem participações.
    ownership: dict[str, Any] = {}
    node = (
        await session.execute(
            text(
                """
                SELECT id::text, name, kind, captable_as_of, captable_newer_act_date,
                       captable_total, captable_currency, captable_complete, acts_count
                  FROM registry_entities WHERE nif = :nif
                """
            ),
            {"nif": nif_norm},
        )
    ).mappings().first()
    if node:
        socios = (
            await session.execute(
                text(
                    """
                    SELECT h.nif, h.name, h.kind,
                           sum(r.quota_amount) AS quota_amount,
                           min(r.currency) AS currency,
                           sum(r.quota_share) AS quota_share,
                           count(*) AS quotas,
                           max(a.act_date) AS act_date
                      FROM registry_edges r
                      JOIN registry_entities h ON h.id = r.holder_id
                      JOIN registry_acts a ON a.id = r.source_act_id
                     WHERE r.subject_id = CAST(:id AS UUID)
                       AND r.edge_type = 'holds_quota' AND r.is_current
                     GROUP BY h.id, h.nif, h.name, h.kind
                     ORDER BY sum(r.quota_amount) DESC NULLS LAST LIMIT :lim
                    """
                ),
                {"id": node["id"], "lim": MAX_ITEMS},
            )
        ).mappings().all()
        cargos = (
            await session.execute(
                text(
                    """
                    SELECT h.nif, h.name, r.role_label, r.edge_type, r.valid_from
                      FROM registry_edges r
                      JOIN registry_entities h ON h.id = r.holder_id
                     WHERE r.subject_id = CAST(:id AS UUID) AND r.is_current
                       AND r.edge_type NOT IN ('holds_quota', 'spouse_of')
                     ORDER BY r.valid_from DESC NULLS LAST LIMIT :lim
                    """
                ),
                {"id": node["id"], "lim": MAX_ITEMS},
            )
        ).mappings().all()
        # O outro sentido, e com os incidentes de cada participada: é isto que
        # responde a "e as empresas ligadas a este cliente, como estão".
        participacoes = (
            await session.execute(
                text(
                    """
                    SELECT s.nif, s.name, s.kind, r.edge_type, r.role_label,
                           sum(r.quota_amount) AS quota_amount,
                           min(r.currency) AS currency,
                           sum(r.quota_share) AS quota_share,
                           count(*) FILTER (
                               WHERE r.edge_type = 'holds_quota') AS quotas,
                           bool_or(i.insolvency_open) AS insolvency_open,
                           min(i.open_claim_deadline) AS open_claim_deadline,
                           max(i.cire_ann_as_debtor) AS cire_ann_as_debtor,
                           min(i.monitoring_type) AS monitoring_type
                      FROM registry_edges r
                      JOIN registry_entities s ON s.id = r.subject_id
                      LEFT JOIN entity_incident_summary i ON i.nif = s.nif
                     WHERE r.holder_id = CAST(:id AS UUID) AND r.is_current
                       AND r.edge_type <> 'spouse_of'
                     GROUP BY s.id, s.nif, s.name, s.kind, r.edge_type, r.role_label
                     ORDER BY sum(r.quota_amount) DESC NULLS LAST LIMIT :lim
                    """
                ),
                {"id": node["id"], "lim": MAX_ITEMS},
            )
        ).mappings().all()
        ownership = {
            "entidade": dict(node),
            "socios": [dict(r) for r in socios],
            "cargos": [dict(r) for r in cargos],
            "participacoes": [dict(r) for r in participacoes],
        }

    from app.services.registry_query import coverage as registry_coverage

    return {
        "nif": nif_norm,
        "rede": ownership,
        # A cobertura do índice viaja com o dossiê: enquanto o varrimento nacional
        # não fechar, uma lista de participações vazia não prova que não existam,
        # e quem consome isto tem de poder dizer isso a quem lê.
        "cobertura": await registry_coverage(session),
        # `monitorizada` a false não significa "sem risco": significa que esta
        # entidade nunca foi registada no Intel Grid, portanto processos e
        # publicações não foram recolhidos. A UI tem de dizer isso, senão um
        # dossiê vazio lê-se como "está tudo bem".
        "monitorizada": company is not None,
        "empresa": dict(company) if company else None,
        "prazos_reclamacao": [dict(r) for r in deadlines],
        "anuncios_insolvencia": [dict(r) for r in announcements],
        "processos": processes,
        "processos_contagem": counts,
        "publicacoes": publications,
        "pessoas": people,
        "contratos_publicos": contracts,
    }
