"""Parsing partilhado das páginas CIRE do CITIUS.

Extraído de `cire.py` quando o sweep nacional (`cire_announcements.py`) passou a
precisar exactamente do mesmo parser de blocos. A página é a mesma
(`ConsultasCire.aspx`); muda só o filtro do formulário — por NIF no caso do
`cire.py`, por intervalo de datas no caso do sweep.
"""
import re
from datetime import date, datetime
from typing import Any

from bs4 import BeautifulSoup, NavigableString, Tag

URL = "https://www.citius.mj.pt/portal/consultas/ConsultasCire.aspx"
BASE = "https://www.citius.mj.pt/portal/consultas/"

# Nomes dos campos do formulário (verificados ao vivo, 2026-04-16 e 2026-07-25)
F_TIPO = "ctl00$ContentPlaceHolder1$rblTipo"
F_PESQUISA = "ctl00$ContentPlaceHolder1$txtPesquisa"
F_DIAS = "ctl00$ContentPlaceHolder1$rblDias"
F_TRIBUNAIS = "ctl00$ContentPlaceHolder1$ddlTribunais"
F_GRUPO = "ctl00$ContentPlaceHolder1$ddlGrupoActos"
F_ACTOS = "ctl00$ContentPlaceHolder1$ddlActos"
F_NUMERO = "ctl00$ContentPlaceHolder1$txtNumeroProcesso"
F_DESDE = "ctl00$ContentPlaceHolder1$txtCalendarDesde"
F_ATE = "ctl00$ContentPlaceHolder1$txtCalendarAte"
F_BTN = "ctl00$ContentPlaceHolder1$btnSearch"

# Grupos de actos do ddlGrupoActos
GRUPO_TODOS = ""
GRUPO_PUBLICIDADE = "20"  # Publicidade da Insolvência
GRUPO_ANUNCIOS = "24"  # Anúncios
GRUPO_ATOS_AI = "25"  # Atos do Administrador da Insolvência

RESULT_BLOCK_CLASS = "resultadocdital"
COUNT_RE = re.compile(r"(\d+)\s+documentos\s+encontrados", re.IGNORECASE)
DATE_RE = re.compile(r"(\d{1,2})[/-](\d{1,2})[/-](\d{4})")

# Cada bloco de resultado traz um token opaco que dá acesso ao PDF do anúncio.
# O atributo vem SEM aspas no HTML do CITIUS, daí o `[^\s>]+`.
QUERYSTRING_RE = re.compile(r"<input\s+id='queryString'\s+type='hidden'\s+value=([^\s>]+)\s*/>")


def parse_pt_date(s: str | None) -> date | None:
    if not s:
        return None
    m = DATE_RE.search(s)
    if not m:
        return None
    d, mm, y = m.groups()
    try:
        return datetime(int(y), int(mm), int(d)).date()
    except ValueError:
        return None


def extract_value_after(strong: Tag) -> str:
    """Percorre os irmãos a seguir a um <strong>, parando no próximo <br> ou <strong>."""
    parts: list[str] = []
    sib = strong.next_sibling
    while sib is not None:
        if isinstance(sib, Tag):
            if sib.name in ("br", "strong"):
                break
            parts.append(sib.get_text(" ", strip=True))
        elif isinstance(sib, NavigableString):
            txt = str(sib).strip()
            if txt:
                parts.append(txt)
        sib = sib.next_sibling
    return " ".join(p for p in parts if p).strip()


def parse_block(block: Tag) -> dict[str, Any]:
    """Converte um `div.resultadocdital` num dict de campos etiquetados mais as
    partes aninhadas (Insolvente, Administrador, Credor, …)."""
    fields: dict[str, Any] = {}
    for strong in block.find_all("strong"):
        label = strong.get_text(strip=True).rstrip(":").strip().lower()
        if not label or label.startswith("nif"):
            continue
        value = extract_value_after(strong)
        if not value:
            continue
        # A primeira ocorrência de cada etiqueta ganha
        fields.setdefault(label, value)

    parties: list[dict[str, str]] = []
    for dl in block.find_all("span"):
        sub_id = dl.get("id") or ""
        if "InterDataList" not in sub_id:
            continue
        for inner in dl.find_all("span", recursive=False):
            role = name = nif = None
            for strong in inner.find_all("strong"):
                lbl = strong.get_text(strip=True).rstrip(":").strip().lower()
                val = extract_value_after(strong)
                if "nif" in lbl:
                    nif = val
                elif lbl:
                    role = lbl
                    name = val
            if role or name:
                parties.append({"role": role or "", "name": name or "", "nif": nif or ""})
    fields["_parties"] = parties
    return fields


def parse_cire_page(html: str) -> dict[str, Any]:
    """Parseia uma página de resultados. `tokens` alinha posicionalmente com
    `blocks` — o CITIUS emite um input `queryString` por bloco, pela mesma ordem."""
    soup = BeautifulSoup(html, "lxml")
    container = soup.find(id="ctl00_ContentPlaceHolder1_divResultados") or soup.find(
        id="ctl00_ContentPlaceHolder1_upResultados"
    )
    count_match = COUNT_RE.search(soup.get_text() or "")
    total = int(count_match.group(1)) if count_match else 0
    blocks: list[dict[str, Any]] = []
    if container:
        for block in container.find_all("div", class_=RESULT_BLOCK_CLASS):
            blocks.append(parse_block(block))
    has_next = bool(soup.find(id="ctl00_ContentPlaceHolder1_Pager1_lnkNext"))
    return {
        "total": total,
        "blocks": blocks,
        "has_next": has_next,
        "tokens": QUERYSTRING_RE.findall(html),
    }


def split_process(proc_raw: str) -> tuple[str, str | None]:
    """"869/26.9T8SNT, Juízo de Comércio de Sintra - Juiz 4" -> (número, juízo)."""
    if "," in proc_raw:
        process_number, juizo = proc_raw.split(",", 1)
        return process_number.strip(), juizo.strip() or None
    return proc_raw.strip(), None
