from __future__ import annotations

from datetime import datetime
from decimal import Decimal, InvalidOperation
from xml.etree import ElementTree as ET


def _strip_namespace(tag: str) -> str:
    return tag.rsplit("}", 1)[-1] if "}" in tag else tag


def _first(parent: ET.Element | None, path: str) -> ET.Element | None:
    if parent is None:
        return None
    current = parent
    for part in path.split("/"):
        found = None
        for child in list(current):
            if _strip_namespace(child.tag) == part:
                found = child
                break
        if found is None:
            return None
        current = found
    return current


def _text(parent: ET.Element | None, path: str) -> str | None:
    node = _first(parent, path)
    if node is None or node.text is None:
        return None
    value = node.text.strip()
    return _repair_mojibake(value) if value else None


def _repair_mojibake(value: str) -> str:
    if "Ã" not in value and "Â" not in value:
        return value
    try:
        return value.encode("latin1").decode("utf-8")
    except UnicodeError:
        return value


def _doc(parent: ET.Element | None) -> str | None:
    return _text(parent, "CNPJ") or _text(parent, "CPF") or _text(parent, "NIF")


def _decimal(value: str | None) -> str | None:
    if not value:
        return None
    try:
        return str(Decimal(value))
    except InvalidOperation:
        return None


def _datetime(value: str | None) -> str | None:
    if not value:
        return None
    try:
        parsed = datetime.fromisoformat(value)
    except ValueError:
        return None
    return parsed.strftime("%Y-%m-%d %H:%M:%S")


def _date(value: str | None) -> str | None:
    if not value:
        return None
    try:
        parsed = datetime.strptime(value, "%Y-%m-%d")
    except ValueError:
        return None
    return parsed.strftime("%Y-%m-%d")


def _party(parent: ET.Element | None, papel: str) -> dict[str, str | None]:
    ender = _first(parent, "enderNac") or _first(parent, "end/endNac") or _first(parent, "end")
    return {
        "papel": papel,
        "documento_federal": _doc(parent),
        "nome_razao": _text(parent, "xNome"),
        "municipio_codigo": _text(ender, "cMun"),
        "uf": _text(ender, "UF"),
    }


def parse_nfse_xml(xml_text: str) -> dict[str, object]:
    root = ET.fromstring(xml_text.encode("utf-8"))
    inf = _first(root, "infNFSe")
    dps = _first(inf, "DPS/infDPS")
    emit = _first(inf, "emit")
    prest = _first(dps, "prest")
    toma = _first(dps, "toma")
    inter = _first(dps, "interm")
    serv = _first(dps, "serv")
    serv_codigo = _first(serv, "cServ")
    valores_nfse = _first(inf, "valores")
    valores_dps = _first(dps, "valores")

    prestador_doc = _doc(prest) or _doc(emit)
    tomador_doc = _doc(toma)
    intermediario_doc = _doc(inter)

    fields: dict[str, str | None] = {
        "numero_nfse": _text(inf, "nNFSe"),
        "numero_dfse": _text(inf, "nDFSe"),
        "serie_dps": _text(dps, "serie"),
        "numero_dps": _text(dps, "nDPS"),
        "data_emissao": _datetime(_text(dps, "dhEmi")),
        "data_competencia": _date(_text(dps, "dCompet")),
        "data_processamento": _datetime(_text(inf, "dhProc")),
        "municipio_emissor_codigo": _text(dps, "cLocEmi"),
        "municipio_incidencia_codigo": _text(inf, "cLocIncid"),
        "municipio_prestacao_codigo": _text(serv, "locPrest/cLocPrestacao"),
        "prestador_documento": prestador_doc,
        "prestador_nome": _text(emit, "xNome"),
        "tomador_documento": tomador_doc,
        "tomador_nome": _text(toma, "xNome"),
        "intermediario_documento": intermediario_doc,
        "intermediario_nome": _text(inter, "xNome"),
        "status_documento": _text(inf, "cStat"),
        "codigo_tributacao_nacional": _text(serv_codigo, "cTribNac"),
        "descricao_servico": _text(serv_codigo, "xDescServ"),
        "codigo_nbs": _text(serv_codigo, "cNBS"),
        "descricao_tributacao_nacional": _text(inf, "xTribNac"),
        "descricao_nbs": _text(inf, "xNBS"),
        "valor_servico": _decimal(_text(valores_dps, "vServPrest/vServ")),
        "valor_base_calculo": _decimal(_text(valores_nfse, "vBC")),
        "aliquota": _decimal(_text(valores_nfse, "pAliqAplic")),
        "valor_issqn": _decimal(_text(valores_nfse, "vISSQN")),
        "valor_total_retido": _decimal(_text(valores_nfse, "vTotalRet")),
        "valor_liquido": _decimal(_text(valores_nfse, "vLiq")),
    }

    prestador_party = _party(emit or prest, "prestador")
    prestador_party["documento_federal"] = prestador_doc
    parties = [prestador_party]
    if tomador_doc:
        parties.append(_party(toma, "tomador"))
    if intermediario_doc:
        parties.append(_party(inter, "intermediario"))

    return {"fields": fields, "parties": parties}


def _find_text_by_names(root: ET.Element, names: tuple[str, ...]) -> str | None:
    wanted = set(names)
    for node in root.iter():
        if _strip_namespace(node.tag) in wanted and node.text:
            value = node.text.strip()
            if value:
                return _repair_mojibake(value)
    return None


def parse_nfse_event_xml(xml_text: str) -> dict[str, str | None]:
    root = ET.fromstring(xml_text.encode("utf-8"))
    return {
        "chave_acesso_nfse": _find_text_by_names(root, ("chNFSe", "chaveAcessoNFSe", "chaveAcesso")),
        "tipo_evento": _find_text_by_names(root, ("tpEvento", "cEvento", "tipoEvento", "xEvento", "descEvento")),
        "numero_sequencial": _find_text_by_names(root, ("nSeqEvento", "seqEvento", "numeroSequencial")),
        "data_evento": _datetime(
            _find_text_by_names(root, ("dhEvento", "dhRegEvento", "dataEvento", "dhProc"))
        ),
        "descricao_evento": _find_text_by_names(root, ("xEvento", "descEvento", "xMotivo", "motivo")),
        "protocolo": _find_text_by_names(root, ("nProt", "protocolo", "numeroProtocolo")),
    }
