"""Converte os payloads do Webhook da Rubeus para o formato interno do projeto.

A Rubeus envia, via POST/JSON, listas de itens em 4 formatos (gatilhos):
Contato, Registro de Processo, Atividade e Evento
(veja https://docs.rubeus.com.br/webhook/).

O restante do projeto (mapeamento.py / gerar_planilha.py) trabalha com pares
no formato {"oportunidade": {...}, "contato": {...}}, onde a "oportunidade" usa
os mesmos nomes de campo retornados por /api/Registro/listarOportunidades
(processoNome, etapaNome, statusNome, momento, pessoaNome, pessoaTelefone...).

Este modulo:
  1. classifica cada item recebido (classificar_payload);
  2. normaliza Contato e Registro de Processo para o formato interno;
  3. monta a lista final de pares juntando registros + contatos armazenados
     (montar_registros), preservando as MESMAS 24 colunas da planilha.
"""
from __future__ import annotations


# --------------------------------------------------------------------------- #
# Classificacao do payload recebido
# --------------------------------------------------------------------------- #
def classificar_payload(item: dict) -> str:
    """Identifica o tipo de um item do webhook.

    Retorna: 'registro', 'contato', 'atividade', 'evento' ou 'desconhecido'.
    """
    if not isinstance(item, dict):
        return "desconhecido"

    # Registro de Processo: tem 'processo' e/ou a lista 'contatos'/'cursos'.
    if "processo" in item or "contatos" in item or "cursos" in item:
        return "registro"

    # Atividade: tem 'registroProcesso' / 'vencimento' / 'formaContato'.
    if "registroProcesso" in item or "vencimento" in item or "formaContato" in item:
        return "atividade"

    # Evento: tem 'tipo' (com nome do evento) + 'contato' e/ou 'dataAgendamento'.
    if "tipo" in item and ("dataAgendamento" in item or "contato" in item):
        return "evento"

    # Contato: tem 'nome' e dados pessoais (cpf/emails/telefones/dataNascimento).
    if "nome" in item and any(
        k in item for k in ("cpf", "emails", "telefones", "dataNascimento")
    ):
        return "contato"

    return "desconhecido"


def classificar_lote(itens: list) -> dict[str, list[dict]]:
    """Agrupa uma lista de itens recebidos por tipo."""
    grupos: dict[str, list[dict]] = {
        "registro": [], "contato": [], "atividade": [], "evento": [], "desconhecido": []
    }
    for item in itens or []:
        grupos[classificar_payload(item)].append(item)
    return grupos


# --------------------------------------------------------------------------- #
# Helpers
# --------------------------------------------------------------------------- #
def _nome(valor) -> str | None:
    """Extrai 'nome' de um objeto {id, nome} ou devolve o proprio valor."""
    if isinstance(valor, dict):
        return valor.get("nome")
    return valor


def _id(valor) -> str | None:
    if isinstance(valor, dict):
        ident = valor.get("id") or valor.get("codigo")
        return str(ident) if ident is not None else None
    return str(valor) if valor not in (None, "") else None


def _principal(lista, chave_valor="codOferta"):
    """Retorna o item marcado como principal (ou o primeiro) de uma lista."""
    if not isinstance(lista, list) or not lista:
        return None
    principais = [c for c in lista if str(c.get("principal")) == "1"]
    return (principais or lista)[0]


def _telefone_principal(telefones) -> str | None:
    """O webhook envia telefones.principal como string; aceita tambem dict."""
    if not isinstance(telefones, dict):
        return None
    principal = telefones.get("principal")
    if isinstance(principal, dict):
        return principal.get("telefone") or principal.get("numero")
    return principal


def _email_principal(emails) -> str | None:
    if not isinstance(emails, dict):
        return None
    principal = emails.get("principal")
    if isinstance(principal, dict):
        return principal.get("email")
    return principal


# --------------------------------------------------------------------------- #
# Normalizacao Contato (webhook) -> contato interno
# --------------------------------------------------------------------------- #
def normalizar_contato_webhook(c: dict) -> dict:
    """Mantem o contato cru, mas garante os campos lidos pelo mapeamento."""
    if not isinstance(c, dict):
        return {}
    contato = dict(c)
    # datanascimento (minusculo) e usado por algumas instituicoes; mapeamento
    # ja aceita ambos, mas espelhamos para garantir.
    if "dataNascimento" in c and "datanascimento" not in c:
        contato["datanascimento"] = c.get("dataNascimento")
    return contato


# --------------------------------------------------------------------------- #
# Normalizacao Registro de Processo (webhook) -> oportunidade interna
# --------------------------------------------------------------------------- #
def normalizar_registro_webhook(r: dict) -> dict:
    """Converte o 'Registro de Processo' do webhook para a oportunidade interna.

    Usa os MESMOS nomes de campo que /api/Registro/listarOportunidades, para que
    config/mapeamento_campos.json continue valendo sem alteracoes.
    """
    if not isinstance(r, dict):
        return {}

    oferta = _principal(r.get("cursos")) or {}
    nome_oferta = oferta.get("codOferta") or oferta.get("codCurso")

    op: dict = {
        "id": r.get("id"),
        "processo": _id(r.get("processo")),
        "processoNome": _nome(r.get("processo")),
        "momento": r.get("criacao"),
        "responsavelNome": _nome(r.get("responsavel")),
        "origemNome": _nome(r.get("origem")),
        "modalidadeNome": _nome(r.get("modalidade")),
        # 'status' do webhook (Ganho/Perdida/Em andamento) -> coluna Status.
        "status": r.get("status"),
        "statusNome": r.get("status"),
        # O webhook nao traz o nome da etapa do funil no Registro de Processo;
        # usamos o 'resumoAtual' como melhor aproximacao da situacao no funil.
        "etapaNome": _nome(r.get("resumoAtual")),
        "objecaoNome": _nome(r.get("objecao")),
        "observacaoPerda": r.get("observacaoPerda"),
        "formaIngresso": r.get("formaIngresso"),
        "ultimaAlteracaoStatus": r.get("ultimaAlteracaoStatus"),
        "ultimaAlteracaoEtapa": r.get("ultimaAlteracaoStatus"),
        "cursoNome": nome_oferta,
        "ofertaCursoNome": nome_oferta,
        "localOfertaNome": _nome(r.get("localOferta")),
        "unidadeNome": _nome(r.get("unidade")),
        "concorrenteNome": _nome(r.get("concorrente")),
        "notaEnem": r.get("notaEnem"),
        "camposPersonalizados": r.get("camposPersonalizados") or {},
    }
    return {k: v for k, v in op.items() if v is not None}


def _id_contato_principal(registro: dict) -> str | None:
    contatos = registro.get("contatos")
    principal = _principal(contatos)
    if principal:
        ident = principal.get("id") or principal.get("codigo")
        return str(ident) if ident is not None else None
    return None


# --------------------------------------------------------------------------- #
# Montagem dos pares {oportunidade, contato}
# --------------------------------------------------------------------------- #
def _injetar_dados_contato(oportunidade: dict, contato: dict) -> None:
    """Copia nome/telefone/e-mail do contato para a oportunidade.

    As colunas "Nome do Aluno" e "Telefone" leem primeiro a oportunidade
    (pessoaNome / pessoaTelefone), entao espelhamos esses campos aqui.
    """
    if not contato:
        return
    nome = contato.get("nome") or contato.get("nomeSocial")
    if nome and not oportunidade.get("pessoaNome"):
        oportunidade["pessoaNome"] = nome
    tel = _telefone_principal(contato.get("telefones"))
    if tel and not oportunidade.get("pessoaTelefone"):
        oportunidade["pessoaTelefone"] = tel
    if contato.get("id") and not oportunidade.get("pessoa"):
        oportunidade["pessoa"] = str(contato.get("id"))


def montar_registros(registros_store: dict, contatos_store: dict) -> list[dict]:
    """Monta os pares {oportunidade, contato} a partir do que foi armazenado.

    - registros_store / contatos_store: dicts {id: item_cru_do_webhook}.
    - Cada Registro de Processo vira uma linha; o contato principal e resolvido
      pelo id presente em 'contatos' do registro (quando ja recebido).
    """
    # Indexa contatos por id e por codigo, para casar com o que o registro referencia.
    indice_contatos: dict[str, dict] = {}
    for contato in (contatos_store or {}).values():
        if not isinstance(contato, dict):
            continue
        if contato.get("id") is not None:
            indice_contatos[str(contato["id"])] = contato
        if contato.get("codigo"):
            indice_contatos.setdefault(str(contato["codigo"]), contato)

    pares: list[dict] = []
    for registro in (registros_store or {}).values():
        if not isinstance(registro, dict):
            continue
        oportunidade = normalizar_registro_webhook(registro)

        contato_cru = {}
        # Casa o contato principal do registro com o contato armazenado.
        for ref in registro.get("contatos") or []:
            chave = str(ref.get("id") or ref.get("codigo") or "")
            if chave and chave in indice_contatos:
                if str(ref.get("principal")) == "1" or not contato_cru:
                    contato_cru = indice_contatos[chave]
                    if str(ref.get("principal")) == "1":
                        break

        contato = normalizar_contato_webhook(contato_cru) if contato_cru else {}
        _injetar_dados_contato(oportunidade, contato)
        pares.append({"oportunidade": oportunidade, "contato": contato})

    return pares
