import json
import pytest
from pathlib import Path
from pyjuris import (
    clean_string, clean_date, clean_valor_causa, clean_classe,
    classify_tipo, classify_fase, classify_status_geral,
    deduplicate_fontes, detect_polo_inversion, detect_multiplas_ativas,
    normalizar_processo, should_reprocess,
)

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


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


# ---------------------------------------------------------------------------
# clean_string
# ---------------------------------------------------------------------------

def test_none_retorna_none():
    assert clean_string(None) is None

def test_string_vazia_retorna_none():
    assert clean_string("") is None
    assert clean_string("   ") is None

def test_null_str_retorna_none():
    assert clean_string("null") is None
    assert clean_string("N/A") is None
    assert clean_string("n/a") is None

def test_strip_whitespace():
    assert clean_string("  foo  ") == "foo"

def test_normaliza_espacos_multiplos():
    assert clean_string("foo   bar") == "foo bar"

def test_remove_chars_controle():
    assert clean_string("foo\nbar") == "foo bar"
    assert clean_string("foo\tbar") == "foo bar"
    assert clean_string("foo\rbar") == "foo bar"

def test_remove_null_bytes():
    assert clean_string("foo\x00bar") == "foobar"

def test_fix_double_encode_utf8():
    assert clean_string("RelaÃ§Ã£o") == "Relação"

def test_string_normal_preservada():
    assert clean_string("Banco Agibank S.A") == "Banco Agibank S.A"


# ---------------------------------------------------------------------------
# clean_date
# ---------------------------------------------------------------------------

def test_data_iso_preservada():
    assert clean_date("2025-04-11") == "2025-04-11"

def test_data_datetime_normaliza_timezone():
    assert clean_date("2025-09-28T07:02:13+00:00") == "2025-09-28T07:02:13Z"

def test_data_ano_impossivel_retorna_none():
    assert clean_date("6024-12-02") is None
    assert clean_date("1800-01-01") is None

def test_data_none_retorna_none():
    assert clean_date(None) is None
    assert clean_date("") is None
    assert clean_date("null") is None

def test_data_invalida_retorna_none():
    assert clean_date("nao-e-data") is None


# ---------------------------------------------------------------------------
# clean_valor_causa
# ---------------------------------------------------------------------------

def test_valor_dict_com_valor_str():
    result = clean_valor_causa({"valor": "13672.00", "moeda": "R$", "valor_formatado": "R$ 13.672,00"})
    assert result == {"valor_causa": 13672.00, "moeda": "BRL"}

def test_valor_float():
    assert clean_valor_causa(13672.00) == {"valor_causa": 13672.00, "moeda": "BRL"}

def test_valor_str():
    assert clean_valor_causa("13672.00") == {"valor_causa": 13672.00, "moeda": "BRL"}

def test_valor_zero_retorna_none():
    assert clean_valor_causa(0) is None
    assert clean_valor_causa(0.0) is None

def test_valor_none_retorna_none():
    assert clean_valor_causa(None) is None
    assert clean_valor_causa({}) is None

def test_valor_negativo_retorna_none():
    assert clean_valor_causa(-100.0) is None


# ---------------------------------------------------------------------------
# clean_classe
# ---------------------------------------------------------------------------

def test_classe_remove_codigo_cnj():
    assert clean_classe("PROCEDIMENTO COMUM CÍVEL (7)") == "PROCEDIMENTO COMUM CÍVEL"
    assert clean_classe("PROCEDIMENTO DO JUIZADO ESPECIAL CÍVEL (436)") == "PROCEDIMENTO DO JUIZADO ESPECIAL CÍVEL"

def test_classe_remove_prefixo_colchetes():
    assert clean_classe("[CÍVEL] PROCEDIMENTO COMUM CÍVEL (7)") == "PROCEDIMENTO COMUM CÍVEL"

def test_classe_remove_breadcrumb():
    assert clean_classe("Procedimento Comum Cível « PROCESSO") == "PROCEDIMENTO COMUM CÍVEL"

def test_classe_remove_prefixo_numerico():
    assert clean_classe("7 - PROCEDIMENTO COMUM CÍVEL") == "PROCEDIMENTO COMUM CÍVEL"

def test_classe_uppercase():
    assert clean_classe("Procedimento Comum Cível") == "PROCEDIMENTO COMUM CÍVEL"
    assert clean_classe("Apelação Cível") == "APELAÇÃO CÍVEL"

def test_classe_none_retorna_none():
    assert clean_classe(None) is None
    assert clean_classe("") is None

def test_classe_aplica_equivalencias():
    assert clean_classe("PROCEDIMENTO COMUM") == "PROCEDIMENTO COMUM CÍVEL"
    assert clean_classe("RECURSO INOMINADO") == "RECURSO INOMINADO CÍVEL"
    assert clean_classe("APELAÇÃO") == "APELAÇÃO CÍVEL"

def test_classe_cumprimento_de_sentenca():
    assert clean_classe("CUMPRIMENTO DE SENTENÇA (156)") == "CUMPRIMENTO DE SENTENÇA"


# ---------------------------------------------------------------------------
# classify_tipo / classify_fase / classify_status_geral
# ---------------------------------------------------------------------------

def test_tipo_acao_original():
    assert classify_tipo("PROCEDIMENTO COMUM CÍVEL", grau=1) == "ACAO_ORIGINAL"
    assert classify_tipo("PROCEDIMENTO DO JUIZADO ESPECIAL CÍVEL", grau=1) == "ACAO_ORIGINAL"

def test_tipo_cumprimento():
    assert classify_tipo("CUMPRIMENTO DE SENTENÇA", grau=1) == "CUMPRIMENTO_SENTENCA"
    assert classify_tipo("EXECUÇÃO", grau=1) == "CUMPRIMENTO_SENTENCA"

def test_tipo_recurso():
    assert classify_tipo("APELAÇÃO CÍVEL", grau=2) == "RECURSO"
    assert classify_tipo("RECURSO INOMINADO CÍVEL", grau=2) == "RECURSO"

def test_tipo_recurso_intermediario():
    assert classify_tipo("AGRAVO DE INSTRUMENTO", grau=2) == "RECURSO_INTERMEDIARIO"
    assert classify_tipo("EMBARGOS DE DECLARAÇÃO CÍVEL", grau=2) == "RECURSO_INTERMEDIARIO"

def test_tipo_recurso_superior():
    assert classify_tipo("RECURSO ESPECIAL", grau=3) == "RECURSO_SUPERIOR"
    assert classify_tipo("AGRAVO EM RECURSO ESPECIAL", grau=3) == "RECURSO_SUPERIOR"
    assert classify_tipo("RECURSO EXTRAORDINÁRIO", grau=3) == "RECURSO_SUPERIOR"

def test_tipo_desconhecido():
    assert classify_tipo(None, grau=1) == "DESCONHECIDO"
    assert classify_tipo("CLASSE EXOTICA XYZ", grau=1) == "DESCONHECIDO"

def test_fase_primeiro_grau():
    assert classify_fase([{"grau": 1, "status": "ATIVO"}, {"grau": 1, "status": "INATIVO"}]) == "PRIMEIRO_GRAU"

def test_fase_segundo_grau():
    assert classify_fase([{"grau": 1, "status": "INATIVO"}, {"grau": 2, "status": "ATIVO"}]) == "SEGUNDO_GRAU"

def test_fase_encerrado():
    assert classify_fase([{"grau": 1, "status": "INATIVO"}]) == "ENCERRADO"
    assert classify_fase([]) == "ENCERRADO"

def test_status_geral_ativo():
    assert classify_status_geral([{"status": "INATIVO"}, {"status": "ATIVO"}]) == "ATIVO"

def test_status_geral_inativo():
    assert classify_status_geral([{"status": "INATIVO"}, {"status": "INATIVO"}]) == "INATIVO"


# ---------------------------------------------------------------------------
# deduplicate_fontes
# ---------------------------------------------------------------------------

def _fonte(id, grau, sigla, classe, orgao, data_mov):
    return {
        "id": id, "grau": grau, "sigla": sigla,
        "classe_normalizada": classe, "orgao_julgador": orgao,
        "data_ultima_movimentacao": data_mov, "partes": [],
    }


def test_sem_duplicatas_passa_intacto():
    fontes = [
        _fonte(1, 1, "TJSP", "PROCEDIMENTO COMUM CÍVEL", "1ª Vara", "2025-09-01"),
        _fonte(70, 2, "TJSP", "APELAÇÃO CÍVEL", "13ª Câmara", "2025-10-01"),
    ]
    result, fenomenos = deduplicate_fontes(fontes)
    assert len(result) == 2
    assert fenomenos == []

def test_duplicata_ingestao_mesmo_id():
    fontes = [
        _fonte(5890, 2, "TJMG", "AGRAVO DE INSTRUMENTO", "Câmara X", "2025-08-01"),
        _fonte(5890, 2, "TJMG", "AGRAVO DE INSTRUMENTO", "Câmara X", "2025-08-01"),
    ]
    result, fenomenos = deduplicate_fontes(fontes)
    assert len(result) == 1
    assert any("duplicata_ingestao" in f for f in fenomenos)

def test_redistribuicao():
    fontes = [
        _fonte(1, 1, "TJSP", "PROCEDIMENTO COMUM CÍVEL", "1ª Vara Cível", "2025-08-14"),
        _fonte(24804, 1, "TJSP", "PROCEDIMENTO COMUM CÍVEL", "11 CÍVEL", "2025-09-18"),
    ]
    result, fenomenos = deduplicate_fontes(fontes)
    assert len(result) == 1
    assert result[0]["data_ultima_movimentacao"] == "2025-09-18"
    assert any("redistribuicao" in f for f in fenomenos)

def test_recursos_distintos_preserva_ambos():
    fontes = [
        _fonte(5890, 2, "TJMG", "AGRAVO DE INSTRUMENTO", "Câmara X", "2025-08-01"),
        _fonte(6000, 2, "TJMG", "APELAÇÃO CÍVEL", "Câmara X", "2025-09-01"),
    ]
    result, fenomenos = deduplicate_fontes(fontes)
    assert len(result) == 2
    assert any("recursos_distintos" in f for f in fenomenos)

def test_crawl_duplicado_mantém_maior_id():
    fontes = [
        _fonte(100, 1, "TJRS", "PROCEDIMENTO COMUM CÍVEL", "Vara X", "2025-08-01"),
        _fonte(200, 1, "TJRS", "PROCEDIMENTO COMUM CÍVEL", "Vara X", "2025-08-01"),
    ]
    result, fenomenos = deduplicate_fontes(fontes)
    assert len(result) == 1
    assert result[0]["id"] == 200
    assert any("crawl_duplicado" in f for f in fenomenos)


# ---------------------------------------------------------------------------
# detect_polo_inversion / detect_multiplas_ativas
# ---------------------------------------------------------------------------

def _parte(nome, polo, doc=None):
    return {"nome": nome, "polo": polo, "documento": doc}


def test_inversao_detectada():
    instancias = [
        {"grau": 1, "partes": [_parte("Maria", "ATIVO", "111"), _parte("Banco", "PASSIVO", "222")]},
        {"grau": 2, "partes": [_parte("Banco", "ATIVO", "222"), _parte("Maria", "PASSIVO", "111")]},
    ]
    assert "inversao_polo:222" in detect_polo_inversion(instancias)

def test_sem_inversao():
    instancias = [
        {"grau": 1, "partes": [_parte("Maria", "ATIVO", "111"), _parte("Banco", "PASSIVO", "222")]},
        {"grau": 2, "partes": [_parte("Maria", "ATIVO", "111"), _parte("Banco", "PASSIVO", "222")]},
    ]
    assert detect_polo_inversion(instancias) == []

def test_multiplas_ativas():
    assert detect_multiplas_ativas([{"status": "ATIVO"}, {"status": "ATIVO"}]) is True
    assert detect_multiplas_ativas([{"status": "INATIVO"}, {"status": "ATIVO"}]) is False


# ---------------------------------------------------------------------------
# normalizar_processo
# ---------------------------------------------------------------------------

def test_normalizar_schema_version():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    assert result["schema_version"] == "1.0"

def test_normalizar_numero_cnj():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    assert result["numero_cnj"] == "1234567-89.2025.8.26.0100"

def test_normalizar_tipo_principal():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    assert result["tipo_principal"] == "ACAO_ORIGINAL"

def test_normalizar_fase_atual():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    assert result["fase_atual"] == "PRIMEIRO_GRAU"

def test_normalizar_status_geral():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    assert result["status_geral"] == "ATIVO"

def test_normalizar_total_instancias():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    assert result["total_instancias"] == 1

def test_normalizar_instancia_estrutura():
    inst = normalizar_processo(load_fixture("payload_simples.json"))["instancias"][0]
    assert inst["grau"] == 1
    assert inst["tipo"] == "ACAO_ORIGINAL"
    assert inst["classe"] == "PROCEDIMENTO COMUM CÍVEL"
    assert inst["capa_disponivel"] is True
    assert inst["valor_causa"] == 5000.00
    assert inst["moeda"] == "BRL"

def test_normalizar_partes():
    partes = normalizar_processo(load_fixture("payload_simples.json"))["instancias"][0]["partes"]
    nomes = [p["nome"] for p in partes]
    assert "Joao Silva" in nomes
    assert "Banco Agibank S.A" in nomes

def test_normalizar_etl_metadata():
    result = normalizar_processo(load_fixture("payload_simples.json"))
    assert result["_etl"]["fonte_origem"] == "escavador"
    assert "processado_em" in result["_etl"]

def test_normalizar_cnj_invalido():
    payload = load_fixture("payload_simples.json")
    payload["numero_cnj"] = "invalido"
    assert "cnj_invalido" in normalizar_processo(payload)["fenomenos_detectados"]

def test_normalizar_inversao_polo():
    fenomenos = normalizar_processo(load_fixture("payload_inversao_polo.json"))["fenomenos_detectados"]
    assert any("inversao_polo" in f for f in fenomenos)

def test_normalizar_multiplas_instancias_ativas():
    assert normalizar_processo(load_fixture("payload_inversao_polo.json"))["tem_multiplas_instancias_ativas"] is True

def test_normalizar_sem_fontes():
    payload = load_fixture("payload_simples.json")
    payload["fontes"] = []
    result = normalizar_processo(payload)
    assert "sem_fontes" in result["fenomenos_detectados"]
    assert result["total_instancias"] == 0


# ---------------------------------------------------------------------------
# should_reprocess
# ---------------------------------------------------------------------------

def test_reprocessa_quando_mais_recente():
    assert should_reprocess(stored="2025-09-01", incoming="2025-10-01") is True

def test_skip_quando_igual():
    assert should_reprocess(stored="2025-10-01", incoming="2025-10-01") is False

def test_skip_quando_mais_antigo():
    assert should_reprocess(stored="2025-10-01", incoming="2025-09-01") is False

def test_reprocessa_quando_stored_none():
    assert should_reprocess(stored=None, incoming="2025-10-01") is True

def test_reprocessa_quando_incoming_none():
    assert should_reprocess(stored="2025-10-01", incoming=None) is True


# ---------------------------------------------------------------------------
# build_upsert_params
# ---------------------------------------------------------------------------

def test_upsert_nao_sobrescreve_data_ajuizamento_com_none():
    """ETL não deve sobrescrever data existente com NULL."""
    from pyjuris.outputs.lops_api import build_upsert_params
    payload = load_fixture("payload_simples.json")
    payload["data_inicio"] = None
    params = build_upsert_params(normalizar_processo(payload))
    assert params["data_distribuicao"] is None  # COALESCE no SQL protege o banco


def test_upsert_popula_tipo_principal():
    from pyjuris.outputs.lops_api import build_upsert_params
    result = normalizar_processo(load_fixture("payload_simples.json"))
    params = build_upsert_params(result)
    assert params["tipo_principal"] == "ACAO_ORIGINAL"


def test_upsert_popula_fenomenos_detectados():
    from pyjuris.outputs.lops_api import build_upsert_params
    payload = load_fixture("payload_simples.json")
    payload["numero_cnj"] = "invalido"
    result = normalizar_processo(payload)
    params = build_upsert_params(result)
    assert "cnj_invalido" in params["fenomenos_detectados"]
