#!/usr/bin/env python3
"""
atualizar-catalogo-mensagens - monta web-api/catalogo-mensagens.json a partir
 de arquivos modulares web-api/catalogo-*.json.

Mantem compatibilidade: api.php e pp --api continuam lendo apenas
web-api/catalogo-mensagens.json; este comando gera esse agregado de forma
deterministica a partir dos catalogos de cada modulo/ator.
"""
import argparse
import json
import os
import sys
import tempfile
from pathlib import Path

HEADER_DEFAULT = {
    "tipo": "catalogo-mensagens-sisc",
    "versao": 3,
    "descricao": "Catalogo declarativo SISC agregado automaticamente a partir de web-api/catalogo-*.json.",
    "formatoEscuta": "formatos-escuta/protocolo-mensagem-siscore.v1.json",
    "regraComunicacao": "somente-espaco-sisc-sem-acesso-direto",
    "modeloProdutoFinal": "microservicos-sisc-via-bff-e-catalogo",
}

EXCLUIR = {"catalogo-mensagens.json"}


def uso() -> None:
    print("""Uso:
  pp --atualizar-catalogo-mensagens
  pp --atualizar-catalogo-mensagens --dry-run
  pp --atualizar-catalogo-mensagens --verificar

Descrição:
  Lê todos os arquivos web-api/catalogo-*.json, exceto catalogo-mensagens.json,
  valida ids duplicados e grava o agregado web-api/catalogo-mensagens.json.
  Cada módulo/ator deve manter seu próprio arquivo, por exemplo:
    web-api/catalogo-conector-email.json
    web-api/catalogo-agentes-fixos.json

Regras:
  - O agregado continua existindo por compatibilidade com api.php e pp --api.
  - Arquivos modulares podem conter objeto com chave mensagens:[...] ou uma
    única mensagem com idmensagem/id.
  - A ordem é determinística por nome de arquivo.
""")


def carregar_json(path: Path):
    with path.open("r", encoding="utf-8") as f:
        return json.load(f)


def mensagens_de_fragmento(path: Path):
    data = carregar_json(path)
    if isinstance(data, dict) and isinstance(data.get("mensagens"), list):
        msgs = data["mensagens"]
    elif isinstance(data, dict) and ("idmensagem" in data or "id" in data):
        msgs = [data]
    else:
        raise ValueError(f"{path}: esperado objeto com mensagens[] ou uma mensagem")
    out = []
    for i, m in enumerate(msgs, 1):
        if not isinstance(m, dict):
            raise ValueError(f"{path}: mensagem #{i} nao e objeto")
        mid = m.get("idmensagem") or m.get("id")
        if not isinstance(mid, str) or not mid:
            raise ValueError(f"{path}: mensagem #{i} sem idmensagem/id")
        out.append(m)
    return out


def montar(base: Path):
    web = base / "web-api"
    if not web.is_dir():
        raise ValueError("diretorio web-api/ nao encontrado; execute dentro de um sistema SISC")
    fragmentos = [p for p in sorted(web.glob("catalogo-*.json")) if p.name not in EXCLUIR]
    if not fragmentos:
        raise ValueError("nenhum catalogo modular encontrado em web-api/catalogo-*.json")

    atual_path = web / "catalogo-mensagens.json"
    header = dict(HEADER_DEFAULT)
    if atual_path.is_file():
        try:
            atual = carregar_json(atual_path)
            if isinstance(atual, dict):
                for k in ["tipo", "versao", "descricao", "formatoEscuta", "regraComunicacao", "modeloProdutoFinal"]:
                    if k in atual:
                        header[k] = atual[k]
        except Exception:
            pass
    header["geradoPor"] = "pp --atualizar-catalogo-mensagens"
    header["fontes"] = [f"web-api/{p.name}" for p in fragmentos]

    mensagens = []
    ids = {}
    for frag in fragmentos:
        for m in mensagens_de_fragmento(frag):
            mid = m.get("idmensagem") or m.get("id")
            if mid in ids:
                raise ValueError(f"idmensagem duplicado: {mid} em web-api/{frag.name} e {ids[mid]}")
            ids[mid] = f"web-api/{frag.name}"
            mensagens.append(m)
    header["mensagens"] = mensagens
    return header, fragmentos


def dumps_catalogo(obj) -> str:
    return json.dumps(obj, ensure_ascii=False, indent=2) + "\n"


def main(argv):
    if any(a in ("--ajuda", "--help", "-h") for a in argv[1:]):
        uso(); return 0
    ap = argparse.ArgumentParser(add_help=False)
    ap.add_argument("--dry-run", action="store_true")
    ap.add_argument("--verificar", action="store_true")
    ns, extras = ap.parse_known_args(argv[1:])
    if extras:
        print(f"atualizar-catalogo-mensagens: argumento invalido: {extras[0]}", file=sys.stderr)
        return 2
    base = Path.cwd()
    try:
        catalogo, fragmentos = montar(base)
        texto = dumps_catalogo(catalogo)
        out = base / "web-api" / "catalogo-mensagens.json"
        if ns.dry_run:
            sys.stdout.write(texto)
            return 0
        if ns.verificar:
            atual = out.read_text(encoding="utf-8") if out.is_file() else ""
            if atual != texto:
                print("catalogo-mensagens.json desatualizado; rode pp --atualizar-catalogo-mensagens", file=sys.stderr)
                return 1
            print(f"catalogo-mensagens.json atualizado ({len(catalogo['mensagens'])} mensagem(ns), {len(fragmentos)} fonte(s))")
            return 0
        fd, tmpname = tempfile.mkstemp(prefix="catalogo-mensagens.", suffix=".tmp", dir=str(out.parent))
        try:
            with os.fdopen(fd, "w", encoding="utf-8") as f:
                f.write(texto)
                f.flush()
                os.fsync(f.fileno())
            os.replace(tmpname, out)
        finally:
            if os.path.exists(tmpname):
                os.unlink(tmpname)
        os.chmod(out, 0o664)
        print(f"Catalogo agregado atualizado: web-api/catalogo-mensagens.json")
        print(f"Fontes: {len(fragmentos)} arquivo(s); mensagens: {len(catalogo['mensagens'])}")
        return 0
    except Exception as e:
        print(f"atualizar-catalogo-mensagens: {e}", file=sys.stderr)
        return 1

if __name__ == "__main__":
    raise SystemExit(main(sys.argv))
