"""Inventario de rotas (module/controller/action) do accia, com IDs reais da copia do banco."""
import json
import re
import subprocess
from pathlib import Path
from urllib.parse import urlencode

from config import BANCO, MYSQL_CONTAINER, SAIDA, RAIZ_PROJETOS

# Modulos registrados em config/web.php (operational-old NAO esta registrado).
MODULOS = {
    "performance": "modules/performance/controllers",
    "operational": "modules/operational/controllers",
    "contract": "modules/contract/controllers",
    "task": "modules/task/controllers",
    "risk": "modules/risk/controllers",
    "financial": "modules/financial/controllers",
    "api": "modules/api/controllers",
    # registrado no nivel da aplicacao por OperationalModule::init()
    "configuracoes": "modules/operational/modules/configuracoes/controllers",
}

_RE_ACTION = re.compile(r"public\s+function\s+action([A-Z]\w*)\s*\(([^)]*)\)")
_RE_PARAM = re.compile(r"\$(\w+)\s*(=)?")


def _camel_para_id(nome: str) -> str:
    return re.sub(r"(?<!^)(?=[A-Z])", "-", nome).lower()


def _params(assinatura: str) -> list:
    params = []
    for parte in [p for p in assinatura.split(",") if p.strip()]:
        m = _RE_PARAM.search(parte)
        if m:
            params.append({"nome": m.group(1), "default": m.group(2) is not None})
    return params


def _acoes_do_arquivo(arquivo: Path, modulo, raiz: Path) -> list:
    controller_id = _camel_para_id(arquivo.stem[: -len("Controller")])
    texto = arquivo.read_text(encoding="utf8", errors="replace")
    acoes = []
    for m in _RE_ACTION.finditer(texto):
        if m.group(1) == "s":  # actions()
            continue
        acoes.append({
            "modulo": modulo,
            "controller_id": controller_id,
            "action_id": _camel_para_id(m.group(1)),
            "params": _params(m.group(2)),
            "arquivo": arquivo.relative_to(raiz).as_posix(),
            "linha": texto.count("\n", 0, m.start()) + 1,
        })
    return acoes


def extrair_actions(raiz: Path) -> list:
    raiz = Path(raiz)
    acoes = []
    for arquivo in sorted((raiz / "controllers").glob("*Controller.php")):
        acoes += _acoes_do_arquivo(arquivo, None, raiz)
    for modulo, rel in MODULOS.items():
        for arquivo in sorted((raiz / rel).glob("*Controller.php")):
            acoes += _acoes_do_arquivo(arquivo, modulo, raiz)
    return acoes


def montar_url(modulo, controller_id, action_id, valores: dict) -> str:
    caminho = "/" + "/".join(p for p in (modulo, controller_id, action_id) if p)
    return caminho + ("?" + urlencode(valores) if valores else "")


# ---------- resolucao de IDs reais ----------

def _mysql(sql: str) -> list:
    r = subprocess.run(
        ["docker", "exec", "-i", MYSQL_CONTAINER, "sh", "-c", f'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -N -B {BANCO} 2>/dev/null'],
        input=sql, capture_output=True, text=True, check=True)
    return [l.split("\t") for l in r.stdout.splitlines() if l]


def _tabelas_com_id() -> set:
    return {t for (t,) in _mysql(
        f"select table_name from information_schema.columns where table_schema='{BANCO}' and column_name='id';")}


def _mapa_model_tabela(raiz: Path) -> dict:
    mapa = {}
    for arq in list(raiz.glob("models/**/*.php")) + list(raiz.glob("modules/**/models/**/*.php")):
        m = re.search(r"function\s+tableName\s*\(\)\s*\{\s*return\s*['\"](?:\{\{%)?(\w+)(?:\}\})?['\"]",
                      arq.read_text(encoding="utf8", errors="replace"))
        if m:
            mapa.setdefault(arq.stem, m.group(1))
    return mapa


def _model_do_find_model(arquivo: Path) -> str:
    m = re.search(r"function\s+findModel\s*\([^)]*\)\s*\{.*?(\w+)::find(?:One)?\(", arquivo.read_text(
        encoding="utf8", errors="replace"), re.S)
    return m.group(1) if m else None


def _ids_reais(tabelas: set) -> dict:
    ids = {}
    for t in sorted(tabelas):
        linhas = _mysql(f"select min(id), max(id), count(*) from `{t}`;")
        if linhas and linhas[0][0] != "NULL":
            mn, mx, qt = linhas[0]
            med = _mysql(f"select id from `{t}` order by id limit 1 offset {int(qt) // 2};")
            ids[t] = sorted({int(mn), int(mx), int(med[0][0])})
    return ids


def _tabela_do_param(nome: str, tabela_controller, tabelas: set):
    if nome == "id":
        return tabela_controller
    base = re.sub(r"(_id|Id)$", "", nome)
    if base == nome:
        return None
    snake = _camel_para_id(base).replace("-", "_")
    return snake if snake in tabelas else None


def gerar(raiz: Path, saida_json: Path) -> dict:
    raiz = Path(raiz)
    acoes = extrair_actions(raiz)
    tabelas = _tabelas_com_id()
    model_tab = _mapa_model_tabela(raiz)
    necessarias, por_acao = set(), []
    for a in acoes:
        model = _model_do_find_model(raiz / a["arquivo"])
        tab_ctrl = model_tab.get(model) if model else None
        tab_ctrl = tab_ctrl if tab_ctrl in tabelas else None
        resol = {}
        for p in a["params"]:
            if not p["default"]:
                resol[p["nome"]] = _tabela_do_param(p["nome"], tab_ctrl, tabelas)
        necessarias |= {t for t in resol.values() if t}
        por_acao.append((a, resol))
    ids = _ids_reais(necessarias)
    urls = []
    for a, resol in por_acao:
        obrig = [p["nome"] for p in a["params"] if not p["default"]]
        nao_inferidos = [n for n in obrig if not resol.get(n) or resol[n] not in ids]
        a["param_nao_inferido"] = nao_inferidos
        if not obrig or nao_inferidos:
            fixos = {n: ids[resol[n]][0] for n in obrig if n not in nao_inferidos}
            urls.append(montar_url(a["modulo"], a["controller_id"], a["action_id"], fixos))
            continue
        variantes = max(len(ids[resol[n]]) for n in obrig)
        for i in range(variantes):
            valores = {n: ids[resol[n]][min(i, len(ids[resol[n]]) - 1)] for n in obrig}
            urls.append(montar_url(a["modulo"], a["controller_id"], a["action_id"], valores))
    resultado = {"actions": acoes, "urls": sorted(set(urls)), "ids_por_tabela": ids}
    Path(saida_json).write_text(json.dumps(resultado, ensure_ascii=False, indent=1), encoding="utf8")
    return resultado


if __name__ == "__main__":
    SAIDA.mkdir(parents=True, exist_ok=True)
    r = gerar(RAIZ_PROJETOS / "apps" / "accia", SAIDA / "inventario.json")
    sem = sum(1 for a in r["actions"] if a["param_nao_inferido"])
    print(f"actions={len(r['actions'])} urls={len(r['urls'])} actions_com_param_nao_inferido={sem} tabelas_com_ids={len(r['ids_por_tabela'])}")
