"""
Pipeline ETL — roteia entre input_template e output_template.

Uso como biblioteca:
    from pyjuris import normalizar_processo
    result = normalizar_processo(payload)
    result = normalizar_processo(payload, input_template="escavador", output_template="lops_api")

Uso como CLI:
    etl-lops < input.json
    etl-lops -i ./jsons/ -o ./output/
    etl-lops --api
    etl-lops --resumo < input.json
"""

from pyjuris.inputs import get_parser
from pyjuris.orgaos import parsear_orgao
from pyjuris.outputs import get_serializer
from pyjuris.types import ProcessoCanonical, ProcessoLopsAPI

ETL_VERSION = "1.0.0"


def normalizar_processo(
    payload: dict,
    input_template: str = "escavador",
    output_template: str = "lops_api",
    enrich_orgaos: bool = True,
) -> ProcessoLopsAPI:
    """
    Normaliza payload bruto de um processo judicial.
    Stateless, determinístico, sem efeitos colaterais.

    Args:
        payload:         dict bruto da fonte de dados
        input_template:  parser a usar ("escavador" | "solucionare")
        output_template: formato de saída ("lops_api" | "solucionare")
        enrich_orgaos:   se True (padrão), adiciona `orgao_julgador_info`
                         com tipo, numero, especialidade e localidade em
                         cada instância

    Returns:
        dict no formato definido pelo output_template
    """
    parse = get_parser(input_template)
    serialize = get_serializer(output_template)
    canonical = parse(payload)
    result = serialize(canonical, ETL_VERSION)

    if enrich_orgaos:
        # DataJud output retorna lista de docs (um por instância);
        # lops_api output retorna um dict com campo "instancias".
        docs = result if isinstance(result, list) else [result]
        for doc in docs:
            instancias = doc.get("instancias") or ([doc] if "orgao_julgador" in doc else [])
            for inst in instancias:
                nome = inst.get("orgao_julgador") or (doc.get("orgaoJulgador") or {}).get("nome")
                inst["orgao_julgador_info"] = parsear_orgao(nome) if nome else None

    return result


def should_reprocess(stored: str | None, incoming: str | None) -> bool:
    """
    Compara data_ultima_movimentacao.
    Retorna True se deve reprocessar; False se pode pular.
    """
    if stored is None or incoming is None:
        return True
    return incoming > stored


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

def _formatar_resumo(result: dict) -> str:
    """Formata resultado do ETL em texto legível para humanos."""
    from pyjuris.fenomenos import descrever

    linhas = []
    sep = "─" * 56

    linhas.append(sep)
    linhas.append(f"  Processo   {result.get('numero_cnj', '—')}")
    linhas.append(f"  Tipo       {result.get('tipo_principal', '—')}")
    linhas.append(f"  Fase       {result.get('fase_atual', '—')}")
    linhas.append(f"  Status     {result.get('status_geral', '—')}")

    polo_a = result.get("polo_ativo") or "—"
    polo_p = result.get("polo_passivo") or "—"
    linhas.append(f"  Polo ativo   {polo_a}")
    linhas.append(f"  Polo passivo {polo_p}")

    n = result.get("total_instancias", 0)
    multiplas = " ⚠ múltiplas ativas" if result.get("tem_multiplas_instancias_ativas") else ""
    linhas.append(f"  Instâncias {n}{multiplas}")

    data_i = result.get("data_inicio") or "—"
    data_m = result.get("data_ultima_movimentacao") or "—"
    linhas.append(f"  Distribuição        {data_i}")
    linhas.append(f"  Última movimentação {data_m}")

    fenomenos = result.get("fenomenos_detectados") or []
    if fenomenos:
        linhas.append("")
        linhas.append("  Alertas:")
        for f in fenomenos:
            linhas.append(f"    ⚠  {descrever(f)}")

    linhas.append(sep)
    return "\n".join(linhas)


def _cli_main() -> None:
    """Entry point do CLI — usado como script e como `etl-escavador` instalado."""
    import argparse
    import json
    import sys

    parser = argparse.ArgumentParser(
        description="ETL Escavador: transforma payload bruto da API em JSON padronizado."
    )
    _MONGO_DEFAULT = "mongodb://root:wrJWir8uMmPGY2jwPRfgShLaXkG9VJ9p13LfbMDLhspwxiAI7s9BIJjQ5wbUsxXy@147.93.5.227:27018/?directConnection=true"

    parser.add_argument("-i", "--input", metavar="ARQUIVO", nargs="+", help="Arquivo(s) JSON de entrada ou diretório (padrão: stdin)")
    parser.add_argument("-o", "--output", metavar="ARQUIVO", help="Arquivo ou diretório de saída (padrão: stdout)")
    parser.add_argument("--api", action="store_true", help="Inicia servidor HTTP na porta 5045")
    parser.add_argument("--validate", action="store_true", help="Valida amostra do MongoDB contra o transform()")
    parser.add_argument("--amostra", type=int, default=5000, metavar="N", help="Tamanho da amostra para --validate (padrão: 5000)")
    parser.add_argument("--mongo-uri", default=_MONGO_DEFAULT, metavar="URI", help="URI do MongoDB para --validate")
    parser.add_argument("--input-template", default="escavador", metavar="TEMPLATE", help="Input template a usar (padrão: escavador)")
    parser.add_argument("--output-template", default="lops_api", metavar="TEMPLATE", help="Output template a usar (padrão: lops_api)")
    parser.add_argument("--resumo", action="store_true", help="Exibe resumo legível em vez do JSON completo")
    args = parser.parse_args()

    if args.validate:
        import time
        from collections import Counter
        from pymongo import MongoClient

        # Cores ANSI
        GREEN  = "\033[92m"
        YELLOW = "\033[93m"
        RED    = "\033[91m"
        RESET  = "\033[0m"
        BOLD   = "\033[1m"

        def _is_blank(v):
            return v is None or v == "" or v == []

        def _validate_result(result: dict) -> tuple[str, list[str]]:
            """
            Valida o JSON de saída do transform().
            Retorna ("OK"|"WARN"|"ERROR", [motivos]).
            """
            errors = []
            warns  = []

            # --- Campos críticos → ERROR ---
            fenomenos = result.get("fenomenos_detectados", [])

            if "cnj_invalido" in fenomenos:
                errors.append("cnj_invalido")

            if result.get("tipo_principal") in (None, "", "DESCONHECIDO"):
                todas_sem_classe = all(
                    _is_blank(i.get("classe")) for i in result.get("instancias", [{}])
                )
                if todas_sem_classe:
                    warns.append("tipo_principal=DESCONHECIDO (classe ausente na fonte)")
                else:
                    errors.append("tipo_principal=DESCONHECIDO")

            if _is_blank(result.get("fase_atual")):
                errors.append("fase_atual ausente")

            if _is_blank(result.get("status_geral")):
                errors.append("status_geral ausente")

            if result.get("total_instancias", 0) == 0:
                errors.append("total_instancias=0")

            for inst in result.get("instancias", []):
                if _is_blank(inst.get("grau")):
                    errors.append("instancia.grau ausente")
                    break
                if _is_blank(inst.get("classe")):
                    sig = (inst.get("tribunal") or {}).get("sigla") or ""
                    if inst.get("tipo") == "DIARIO_OFICIAL":
                        warns.append("instancia.classe ausente (DIARIO_OFICIAL)")
                    else:
                        warns.append(f"instancia.classe ausente na fonte ({sig})")
                    break
                if _is_blank(inst.get("status")):
                    errors.append("instancia.status ausente")
                    break

            # --- Campos importantes → WARN ---
            if _is_blank(result.get("data_inicio")):
                warns.append("data_inicio ausente")

            if _is_blank(result.get("data_ultima_movimentacao")):
                warns.append("data_ultima_movimentacao ausente")

            if _is_blank(result.get("polo_ativo")):
                warns.append("polo_ativo ausente")

            if _is_blank(result.get("polo_passivo")):
                warns.append("polo_passivo ausente")

            for inst in result.get("instancias", []):
                if _is_blank(inst.get("orgao_julgador")):
                    warns.append("instancia.orgao_julgador ausente")
                    break
                if _is_blank(inst.get("assunto_principal")):
                    warns.append("instancia.assunto_principal ausente")
                    break
                if _is_blank(inst.get("partes")):
                    warns.append("instancia.partes vazio")
                    break

            if errors:
                return "ERROR", errors
            if warns:
                return "WARN", warns
            return "OK", []

        print(f"Conectando ao MongoDB...", end=" ", flush=True)
        client = MongoClient(args.mongo_uri, serverSelectionTimeoutMS=10000)
        col = client["lops_db"]["processos"]
        total_col = col.count_documents({})
        print(f"OK ({total_col:,} documentos)\n")

        docs = col.find({}, no_cursor_timeout=False)

        n_ok = n_warn = n_err = 0
        warn_dist: Counter = Counter()
        err_dist:  Counter = Counter()
        w = len(str(total_col))

        t_inicio = time.time()

        for i, doc in enumerate(docs, 1):
            cnj = doc.get("numero_cnj") or f"<sem_cnj:{i}>"
            prefix = f"{i:>{w}}/{total_col}"

            payload = {
                "numero_cnj":               doc.get("numero_cnj"),
                "ano_inicio":               doc.get("ano_inicio"),
                "data_inicio":              doc.get("data_inicio"),
                "data_ultima_movimentacao": doc.get("data_ultima_movimentacao"),
                "data_ultima_verificacao":  doc.get("data_ultima_verificacao"),
                "titulo_polo_ativo":        doc.get("titulo_polo_ativo"),
                "titulo_polo_passivo":      doc.get("titulo_polo_passivo"),
                "fontes":                   doc.get("fontes", []),
            }

            try:
                result = normalizar_processo(payload, args.input_template, args.output_template)
            except Exception as e:
                n_err += 1
                motivo = f"{type(e).__name__}: {str(e)[:80]}"
                err_dist[motivo] += 1
                print(f"{prefix}  {cnj:<30} {RED}ERROR{RESET}", flush=True)
                print(f"           {RED}└─ {motivo}{RESET}", flush=True)
                continue

            nivel, motivos = _validate_result(result)

            if nivel == "OK":
                n_ok += 1
                print(f"{prefix}  {cnj:<30} {GREEN}OK{RESET}", flush=True)
            elif nivel == "WARN":
                n_warn += 1
                for m in motivos:
                    warn_dist[m] += 1
                detalhe = " | ".join(motivos)
                print(f"{prefix}  {cnj:<30} {YELLOW}WARN{RESET}", flush=True)
                print(f"           {YELLOW}└─ {detalhe}{RESET}", flush=True)
            else:
                n_err += 1
                for m in motivos:
                    err_dist[m] += 1
                detalhe = " | ".join(motivos)
                print(f"{prefix}  {cnj:<30} {RED}ERROR{RESET}", flush=True)
                print(f"           {RED}└─ {detalhe}{RESET}", flush=True)

        elapsed = time.time() - t_inicio

        total = n_ok + n_warn + n_err
        def pct(n): return n / total * 100 if total else 0

        sep = "+" + "-" * 10 + "+" + "-" * 8 + "+" + "-" * 9 + "+"
        print(f"\n{BOLD}{sep}")
        print(f"| {'Resultado':<8} | {'Qtd':>6} | {'%':>7} |")
        print(f"{sep}{RESET}")
        print(f"| {GREEN}{'OK':<8}{RESET} | {n_ok:>6} | {pct(n_ok):>6.1f}% |")
        print(f"| {YELLOW}{'WARN':<8}{RESET} | {n_warn:>6} | {pct(n_warn):>6.1f}% |")
        print(f"| {RED}{'ERROR':<8}{RESET} | {n_err:>6} | {pct(n_err):>6.1f}% |")
        print(f"{BOLD}{sep}")
        print(f"| {'Total':<8} | {total:>6} | {'100.0%':>7} |")
        print(f"{sep}{RESET}")

        if warn_dist:
            total_warns = sum(warn_dist.values())
            print(f"\n{YELLOW}{BOLD}WARNs — distribuição:{RESET}")
            for msg, cnt in warn_dist.most_common():
                print(f"  {cnt:5d}x  ({cnt/total_warns*100:5.1f}%)  {msg}")

        if err_dist:
            total_errs = sum(err_dist.values())
            print(f"\n{RED}{BOLD}ERROs — distribuição:{RESET}")
            for msg, cnt in err_dist.most_common():
                print(f"  {cnt:5d}x  ({cnt/total_errs*100:5.1f}%)  {msg}")

        mins, secs = divmod(int(elapsed), 60)
        tempo_fmt = f"{mins}m{secs:02d}s" if mins else f"{secs:.1f}s"
        print(f"\nTempo total: {tempo_fmt}")

    elif args.api:
        from flask import Flask, request, jsonify

        app = Flask(__name__)

        _HTML = """<!doctype html>
<html lang="pt-BR">
<head>
  <meta charset="utf-8">
  <title>ETL Escavador</title>
  <style>
    body { font-family: sans-serif; max-width: 700px; margin: 40px auto; padding: 0 20px; }
    h1 { font-size: 1.4rem; }
    textarea { width: 100%; height: 200px; font-family: monospace; font-size: 0.85rem; }
    button { margin-top: 8px; padding: 8px 20px; cursor: pointer; }
    pre { background: #f4f4f4; padding: 16px; overflow: auto; font-size: 0.8rem; }
    label { display: block; margin-top: 16px; font-weight: bold; }
    hr { margin: 32px 0; }
  </style>
</head>
<body>
  <h1>ETL Escavador</h1>

  <label>Colar JSON:</label>
  <textarea id="payload" placeholder='{"numero_cnj": "...", "fontes": [...]}'></textarea>
  <button onclick="enviarJson()">Transformar</button>

  <hr>

  <label>Upload de arquivo .json:</label>
  <input type="file" id="arquivo" accept=".json">
  <button onclick="enviarArquivo()">Enviar arquivo</button>

  <hr>
  <pre id="resultado">Resultado aparece aqui...</pre>

  <script>
    async function enviarJson() {
      try {
        const payload = JSON.parse(document.getElementById('payload').value);
        const res = await fetch('/transform', {
          method: 'POST',
          headers: {'Content-Type': 'application/json'},
          body: JSON.stringify(payload)
        });
        const data = await res.json();
        document.getElementById('resultado').textContent = JSON.stringify(data, null, 2);
      } catch(e) {
        document.getElementById('resultado').textContent = 'Erro: ' + e.message;
      }
    }

    async function enviarArquivo() {
      const file = document.getElementById('arquivo').files[0];
      if (!file) { alert('Selecione um arquivo'); return; }
      const form = new FormData();
      form.append('file', file);
      const res = await fetch('/upload', {method: 'POST', body: form});
      const data = await res.json();
      document.getElementById('resultado').textContent = JSON.stringify(data, null, 2);
    }
  </script>
</body>
</html>"""

        @app.get("/")
        def index():
            return _HTML, 200, {"Content-Type": "text/html; charset=utf-8"}

        @app.post("/transform")
        def api_transform():
            if not request.is_json:
                return jsonify({"erro": "Content-Type deve ser application/json"}), 400
            try:
                result = normalizar_processo(
                    request.get_json(force=True),
                    args.input_template,
                    args.output_template,
                )
                return jsonify(result)
            except Exception as e:
                return jsonify({"erro": str(e)}), 422

        @app.post("/upload")
        def api_upload():
            if "file" not in request.files:
                return jsonify({"erro": "Campo 'file' não encontrado"}), 400
            try:
                payload = json.load(request.files["file"])
                return jsonify(transform(payload, args.input_template, args.output_template))
            except json.JSONDecodeError as e:
                return jsonify({"erro": f"JSON inválido: {e}"}), 400
            except Exception as e:
                return jsonify({"erro": str(e)}), 422

        print("ETL Escavador API rodando em http://0.0.0.0:5045", file=sys.stderr)
        print("  POST /transform   — body JSON", file=sys.stderr)
        print("  POST /upload      — multipart file upload", file=sys.stderr)
        print("  GET  /            — interface web", file=sys.stderr)
        app.run(host="0.0.0.0", port=5045)

    else:
        from pathlib import Path

        inputs: list[Path] = []
        if args.input:
            for entry in args.input:
                p = Path(entry)
                if p.is_dir():
                    inputs.extend(sorted(p.glob("*.json")))
                else:
                    inputs.append(p)

        if len(inputs) > 1:
            out_dir = Path(args.output) if args.output else Path(".")
            out_dir.mkdir(parents=True, exist_ok=True)
            ok = err = 0
            for src in inputs:
                try:
                    payload = json.loads(src.read_text(encoding="utf-8"))
                    result = normalizar_processo(payload, args.input_template, args.output_template)
                    dest = out_dir / src.name
                    dest.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
                    print(f"  OK  {src} → {dest}", file=sys.stderr)
                    ok += 1
                except Exception as e:
                    print(f"ERRO  {src}: {e}", file=sys.stderr)
                    err += 1
            print(f"\n{ok} processado(s), {err} erro(s).", file=sys.stderr)

        else:
            if inputs:
                payload = json.loads(inputs[0].read_text(encoding="utf-8"))
            else:
                payload = json.load(sys.stdin)

            result = normalizar_processo(payload, args.input_template, args.output_template)

            if args.resumo:
                print(_formatar_resumo(result))
            elif args.output:
                output = json.dumps(result, ensure_ascii=False, indent=2)
                Path(args.output).write_text(output, encoding="utf-8")
                print(f"Salvo em {args.output}", file=sys.stderr)
            else:
                print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    _cli_main()
