"""Preencher a ficha da empresa a partir das publicações do registo comercial.

Três níveis de informação, e é importante não prometer o nível errado:

1. **Qualquer publicação**, mesmo uma simples "PRESTAÇÃO DE CONTAS", traz um
   cabeçalho com NIPC, firma oficial, natureza jurídica e sede. Serve para
   enriquecer empresas de qualquer idade — a SECURITAS, constituída há décadas,
   dá o nome legal actual e a sede a partir de uma prestação de contas de 2026.
2. **Ficha completa** (objecto, capital, sócios com quotas, gerência) só vem na
   publicação de **Constituição**, que existe para empresas constituídas depois
   de ~2006. A SEGUNOR tem (2018, ainda como IGPSPROTEK); a SECURITAS não.
3. Os actos posteriores trazem só o delta (`Artigo(s) alterado(s): 2.º`), que a
   `mj_people_parser` já usa para acompanhar a gerência.

Onde não houver constituição publicada, capital e objecto ficam por preencher —
e isso fica visível na UI, em vez de se inventar um valor.
"""
import logging
import re
from typing import Any

logger = logging.getLogger(__name__)

_TAG_RE = re.compile(r"<[^>]+>")
_WS_LINE_RE = re.compile(r"[ \t]+")


def html_to_text(html: str | None) -> str:
    if not html:
        return ""
    import html as _html

    text = _TAG_RE.sub("\n", html)
    text = _html.unescape(text)
    # Normalizar CR antes de mexer em espaços: com CRLF, um `\r` sobrante ficava
    # pendurado no fim de cada valor e entrava nos nomes e nos montantes.
    text = text.replace("\r\n", "\n").replace("\r", "\n")
    text = _WS_LINE_RE.sub(" ", text)
    return re.sub(r"\n{2,}", "\n", text)


def _labelled(text: str, label: str) -> str | None:
    """Valor de um campo do cabeçalho: a etiqueta fica numa linha, o valor na
    seguinte."""
    m = re.search(rf"{label}\s*:\s*\n\s*(.+)", text, re.IGNORECASE)
    return m.group(1).strip() or None if m else None


def _titlecase_pt(value: str | None) -> str | None:
    """A fonte escreve em maiúsculas e com acentos partidos: "SOCIEDADE ANóNIMA"
    tem mesmo um "ó" minúsculo no meio de tudo em caixa alta. Testar
    `any(islower())` deixaria passar exactamente esse caso, por isso decidimos
    pela maioria — só normalizamos quando o valor é esmagadoramente maiúsculo."""
    if not value:
        return None
    letters = [c for c in value if c.isalpha()]
    if not letters:
        return value
    upper_ratio = sum(1 for c in letters if c.isupper()) / len(letters)
    if upper_ratio < 0.8:
        return value
    minor = {"de", "da", "do", "das", "dos", "e", "em", "por"}
    words = []
    for w in value.lower().split():
        words.append(w if w in minor and words else w.capitalize())
    return " ".join(words)


def parse_publication(html: str | None) -> dict[str, Any]:
    """Campos da empresa contidos numa publicação. Todos opcionais."""
    text = html_to_text(html)
    out: dict[str, Any] = {
        "nipc": None, "firma": None, "natureza_juridica": None, "sede": None,
        "distrito": None, "concelho": None, "freguesia": None, "codigo_postal": None,
        "localidade": None, "objeto_social": None, "capital_social": None,
        "is_constitution": False,
    }
    if not text:
        return out

    out["nipc"] = _labelled(text, r"N[º°o]\s*de\s*Matr[íi]cula\s*/\s*NIPC")
    out["firma"] = _labelled(text, "Firma")
    out["natureza_juridica"] = _titlecase_pt(_labelled(text, r"Natureza\s*Jur[íi]dica"))
    out["sede"] = _labelled(text, "Sede")

    # Distrito/Concelho/Freguesia partilham a linha, com separação variável —
    # nalgumas publicações são dois espaços, noutras um só. O código postal vem
    # na linha seguinte, e o formato oscila entre "2799-553 Linda-A-Velha" e
    # "2799 Linda-A-Velha". Ancoramos na estrutura (a linha a seguir) em vez de
    # procurar o padrão no documento todo, que apanharia números soltos.
    m = re.search(
        r"Distrito\s*:\s*(.*?)\s*Concelho\s*:\s*(.*?)\s*Freguesia\s*:\s*(.*?)[ \t]*\n"
        r"[ \t]*(\d{4})(?:\s*-\s*(\d{3}))?[ \t]+([^\n]+)",
        text, re.IGNORECASE,
    )
    if m:
        out["distrito"] = m.group(1).strip() or None
        out["concelho"] = m.group(2).strip() or None
        out["freguesia"] = m.group(3).strip() or None
        out["codigo_postal"] = f"{m.group(4)}-{m.group(5)}" if m.group(5) else m.group(4)
        out["localidade"] = m.group(6).strip()
    else:
        m = re.search(
            r"Distrito\s*:\s*(.*?)\s*Concelho\s*:\s*(.*?)\s*Freguesia\s*:\s*(.*?)[ \t]*$",
            text, re.IGNORECASE | re.MULTILINE,
        )
        if m:
            out["distrito"] = m.group(1).strip() or None
            out["concelho"] = m.group(2).strip() or None
            out["freguesia"] = m.group(3).strip() or None

    # Corpo da constituição — o único sítio onde objecto e capital aparecem.
    m = re.search(r"^OBJECTO\s*:\s*(.+?)(?=\n[A-ZÇÃÕÉ][A-ZÇÃÕÉ \t]{2,}\s*:)", text, re.MULTILINE | re.DOTALL)
    if m:
        out["objeto_social"] = re.sub(r"\s+", " ", m.group(1)).strip()[:2000]
    m = re.search(r"^CAPITAL\s*:\s*([\d .,]+)\s*Euros", text, re.MULTILINE | re.IGNORECASE)
    if m:
        raw = m.group(1).strip().replace(" ", "").replace(".", "").replace(",", ".")
        try:
            out["capital_social"] = float(raw)
        except ValueError:
            pass

    out["is_constitution"] = bool(re.search(r"CONSTITUI[ÇC][ÃA]O\s+DE\s+SOCIEDADE", text, re.IGNORECASE))
    return out


CONSTITUTION_RE = re.compile(r"constitui[çc][ãa]o\s+de\s+sociedade", re.IGNORECASE)
CAPITAL_CHANGE_RE = re.compile(
    r"(aumento|redu[çc][ãa]o)\s+d[oe]\s+capital", re.IGNORECASE
)


def capital_is_current(pubs: list[dict[str, Any]]) -> bool:
    """True quando o capital da constituição ainda é o capital actual.

    A constituição diz o capital **inicial**. A SEGUNOR foi constituída com
    1.000 EUR em 2018 e aumentou depois — as quotas dos sócios já são de
    125.000 EUR cada. Publicar 1.000 como capital actual seria simplesmente
    errado, e não temos o valor novo (os actos de aumento não repetem o total).
    Nesse caso preferimos não preencher: um campo vazio é honesto, um número
    desactualizado não é.
    """
    return not any(CAPITAL_CHANGE_RE.search(p.get("Act_Fact") or "") for p in pubs)


def select_enrichment_publications(pubs: list[dict[str, Any]]) -> list[dict[str, Any]]:
    """Escolhe as publicações que vale a pena abrir para enriquecer a ficha.

    Puxar o conteúdo de todas seria caro sem ganho: são 2105 publicações nas 94
    empresas monitorizadas e a esmagadora maioria são prestações de contas, que
    repetem o mesmo cabeçalho. Bastam duas:

      * a **mais recente**, para firma, natureza jurídica e sede actuais — o
        cabeçalho vem em qualquer publicação, mesmo numa prestação de contas;
      * a **constituição**, quando existe, que é a única que traz objecto,
        capital, sócios e gerência inicial.

    `pubs` vem do IRN ordenada da mais recente para a mais antiga.
    """
    if not pubs:
        return []
    chosen = [pubs[0]]
    constitution = next(
        (p for p in pubs if CONSTITUTION_RE.search(p.get("Act_Fact") or "")), None
    )
    if constitution is not None and constitution is not pubs[0]:
        chosen.append(constitution)
    return chosen


CURRENT_STATE = (
    "firma", "natureza_juridica", "sede", "distrito", "concelho",
    "freguesia", "codigo_postal", "localidade",
)
FROM_CONSTITUTION = ("objeto_social", "capital_social")


def merge_company_fields(publications: list[dict[str, Any]]) -> dict[str, Any]:
    """Combina as publicações abertas numa ficha só.

    **A identidade e a morada vêm exclusivamente da publicação mais recente**,
    em bloco. Preencher campo a campo a partir de várias publicações produz
    fichas incoerentes: a SEGUNOR ficava com a rua actual e o código postal da
    constituição de 2018, e a firma seria "IGPSPROTEK, LDA", o nome anterior à
    mudança. Um campo em falta é preferível a um campo de outra época.

    Objecto e capital só existem na constituição, e esses sim aceitam-se de lá.

    `publications` deve vir ordenada da mais recente para a mais antiga.
    """
    if not publications:
        return {"has_constitution": False}

    merged: dict[str, Any] = {k: publications[0].get(k) for k in CURRENT_STATE}
    for pub in publications:
        for k in FROM_CONSTITUTION:
            if merged.get(k) is None and pub.get(k) is not None:
                merged[k] = pub[k]
    merged["has_constitution"] = any(p.get("is_constitution") for p in publications)
    return merged
