"""
Testes de DX — fenômenos, resumo, tipos, templates e registry.
"""

import json
import pytest
from pathlib import Path

from pyjuris import (
    normalizar_processo,
    resumo,
    descrever_fenomeno,
    descrever_fenomenos,
    ProcessoCanonical,
    ProcessoLopsAPI,
)
from pyjuris.fenomenos import DESCRICOES
from pyjuris.inputs import get_parser
from pyjuris.outputs import get_serializer
from pyjuris.pipeline import _formatar_resumo

FIXTURES = Path(__file__).parent / "fixtures"


def load_fixture(name):
    return json.loads((FIXTURES / name).read_text())


# ---------------------------------------------------------------------------
# descrever_fenomeno
# ---------------------------------------------------------------------------

def test_descrever_fenomeno_estatico():
    assert descrever_fenomeno("cnj_invalido") == DESCRICOES["cnj_invalido"]


def test_descrever_fenomeno_estatico_sem_fontes():
    assert descrever_fenomeno("sem_fontes") == DESCRICOES["sem_fontes"]


def test_descrever_fenomeno_dinamico_com_sufixo():
    # fenômenos dinâmicos: "inversao_polo:12345678900"
    desc = descrever_fenomeno("inversao_polo:12345678900")
    assert desc == DESCRICOES["inversao_polo"]


def test_descrever_fenomeno_dinamico_crawl_duplicado():
    desc = descrever_fenomeno("crawl_duplicado:grau=1:TJSP")
    assert desc == DESCRICOES["crawl_duplicado"]


def test_descrever_fenomeno_desconhecido_retorna_proprio_codigo():
    codigo = "fenomeno_inexistente_xyz"
    assert descrever_fenomeno(codigo) == codigo


def test_descrever_fenomeno_todos_os_estaticos_tem_descricao():
    estaticos = [
        "cnj_invalido",
        "sem_fontes",
        "multiplas_instancias_ativas",
        "duplicata_ingestao",
        "redistribuicao",
        "crawl_duplicado",
        "recursos_distintos",
        "inversao_polo",
    ]
    for codigo in estaticos:
        desc = descrever_fenomeno(codigo)
        assert desc != codigo, f"Fenômeno '{codigo}' sem descrição registrada"
        assert len(desc) > 10, f"Descrição de '{codigo}' muito curta"


# ---------------------------------------------------------------------------
# descrever_fenomenos (lista)
# ---------------------------------------------------------------------------

def test_descrever_fenomenos_retorna_lista_de_dicts():
    resultado = descrever_fenomenos(["cnj_invalido", "sem_fontes"])
    assert len(resultado) == 2
    assert resultado[0]["codigo"] == "cnj_invalido"
    assert "descricao" in resultado[0]


def test_descrever_fenomenos_lista_vazia():
    assert descrever_fenomenos([]) == []


def test_descrever_fenomenos_preserva_ordem():
    codigos = ["sem_fontes", "cnj_invalido", "multiplas_instancias_ativas"]
    resultado = descrever_fenomenos(codigos)
    assert [r["codigo"] for r in resultado] == codigos


def test_descrever_fenomenos_com_dinamicos():
    codigos = ["inversao_polo:12345", "cnj_invalido"]
    resultado = descrever_fenomenos(codigos)
    assert resultado[0]["descricao"] == DESCRICOES["inversao_polo"]
    assert resultado[1]["descricao"] == DESCRICOES["cnj_invalido"]


# ---------------------------------------------------------------------------
# resumo / _formatar_resumo
# ---------------------------------------------------------------------------

def test_resumo_contem_cnj():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    saida = resumo(result)
    assert result["numero_cnj"] in saida


def test_resumo_contem_polo_ativo():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    saida = resumo(result)
    assert result["polo_ativo"] in saida


def test_resumo_contem_tipo():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    saida = resumo(result)
    assert result["tipo_principal"] in saida


def test_resumo_sem_fenomenos_nao_mostra_alertas():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    # payload_simples não deve ter fenômenos críticos
    if not result.get("fenomenos_detectados"):
        assert "Alertas" not in resumo(result)


def test_resumo_com_inversao_polo_mostra_alerta():
    result = normalizar_processo(load_fixture("payload_inversao_polo.json"))
    saida = resumo(result)
    assert "Alertas" in saida
    assert "⚠" in saida


def test_resumo_multiplas_ativas_exibe_aviso():
    result = normalizar_processo(load_fixture("payload_inversao_polo.json"))
    saida = resumo(result)
    assert "múltiplas ativas" in saida


def test_resumo_retorna_string():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    assert isinstance(resumo(result), str)


def test_resumo_contem_separadores():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    saida = resumo(result)
    assert "─" in saida


# ---------------------------------------------------------------------------
# registry de templates
# ---------------------------------------------------------------------------

def test_get_parser_escavador_retorna_callable():
    parser = get_parser("escavador")
    assert callable(parser)


def test_get_parser_template_invalido_levanta_valueerror():
    with pytest.raises(ValueError, match="não registrado"):
        get_parser("fonte_inexistente")


def test_get_parser_solucionare_registrado():
    # mock existe no registry — deve retornar callable sem erro
    parser = get_parser("solucionare")
    assert callable(parser)


def test_get_parser_solucionare_levanta_not_implemented():
    parser = get_parser("solucionare")
    with pytest.raises(NotImplementedError):
        parser({})


def test_get_serializer_lops_api_retorna_callable():
    serializer = get_serializer("lops_api")
    assert callable(serializer)


def test_get_serializer_template_invalido_levanta_valueerror():
    with pytest.raises(ValueError, match="não registrado"):
        get_serializer("destino_inexistente")


def test_get_serializer_solucionare_registrado():
    serializer = get_serializer("solucionare")
    assert callable(serializer)


def test_get_serializer_solucionare_levanta_not_implemented():
    serializer = get_serializer("solucionare")
    with pytest.raises(NotImplementedError):
        serializer({}, "1.0.0")


# ---------------------------------------------------------------------------
# normalizar_processo com templates explícitos
# ---------------------------------------------------------------------------

def test_normalizar_com_templates_explicitos():
    payload = load_fixture("payload_simples.json")
    result = normalizar_processo(payload, input_template="escavador", output_template="lops_api")
    assert result["schema_version"] == "1.0"
    assert "_etl" in result
    assert result["_etl"]["fonte_origem"] == "escavador"


def test_normalizar_output_template_invalido_levanta_valueerror():
    with pytest.raises(ValueError, match="não registrado"):
        normalizar_processo(load_fixture("payload_simples.json"), output_template="destino_xyz")


def test_normalizar_input_template_invalido_levanta_valueerror():
    with pytest.raises(ValueError, match="não registrado"):
        normalizar_processo(load_fixture("payload_simples.json"), input_template="fonte_xyz")


# ---------------------------------------------------------------------------
# tipos — verificação estrutural
# ---------------------------------------------------------------------------

def test_resultado_tem_campos_de_ProcessoLopsAPI():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    campos_esperados = [
        "numero_cnj", "tipo_principal", "fase_atual", "status_geral",
        "total_instancias", "segredo_justica", "capa_disponivel",
        "tem_audiencia", "tem_multiplas_instancias_ativas",
        "fenomenos_detectados", "instancias",
        "schema_version", "_etl",
    ]
    for campo in campos_esperados:
        assert campo in result, f"Campo '{campo}' ausente no output"


def test_resultado_nao_expoe_campos_internos():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    # _fonte_origem é interno do canonical e não deve aparecer no output lops_api
    assert "_fonte_origem" not in result


def test_instancia_tem_campos_obrigatorios():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    inst = result["instancias"][0]
    campos = ["grau", "grau_formatado", "tipo", "tribunal", "status", "partes"]
    for campo in campos:
        assert campo in inst, f"Campo '{campo}' ausente na instância"


def test_parte_tem_campos_obrigatorios():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    parte = result["instancias"][0]["partes"][0]
    assert "nome" in parte
    assert "polo" in parte
    assert parte["polo"] in ("ATIVO", "PASSIVO", "ADVOGADO", "DESCONHECIDO")
