/*
 * instalar-conector - instala no sistema operacional um conector recebido em testesis
 * Uso: pp --instalar-conector=<nome> [--para=<sistema>] [--sim]
 *
 * Fluxo do servidor para conectores da comunidade:
 *   1. recebe o pacote em testesis/conectores/<nome>/ + testesis/web-api/catalogo-<nome>.json
 *   2. validação interna (manifesto, testeSandbox, handler executável, catálogo)
 *   3. teste sandbox do handler com testes/mensagem-exemplo.json
 *   4. instala no sistema destino (padrão: siscore), traduzindo o manifesto
 *      do layout do kit para o layout operacional
 *   5. regenera web-api/catalogo-mensagens.json e verifica a instalação
 *   6. em caso de sucesso, remove o staging de testesis (a instalação MOVE,
 *      não copia); em caso de falha, o staging é preservado para análise
 */

#define _GNU_SOURCE
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <dirent.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include "../restricao.h"

static void uso(void) {
    fputs(
        "Uso:\n"
        "  pp --instalar-conector=<nome> [--para=<sistema>] [--sim]\n"
        "  pp --instalar-conector <nome>\n"
        "\n"
        "Descrição:\n"
        "  Valida e instala no sistema operacional (padrão: siscore) um conector\n"
        "  recebido em testesis/. Em caso de sucesso o staging de testesis é\n"
        "  removido (a instalação move, não copia); em caso de falha o staging\n"
        "  é preservado para análise e nada é alterado no destino.\n"
        "\n"
        "Etapas:\n"
        "  validacao interna, teste sandbox do handler, instalacao com traducao\n"
        "  do manifesto kit->operacional, regeneracao do catalogo agregado,\n"
        "  verificacao e limpeza do staging.\n"
        "\n"
        "Exemplos:\n"
        "  pp --instalar-conector=conector-tabela-fipe\n"
        "  pp --instalar-conector conector-email --para=siscore --sim\n",
        stdout);
}

static int eh_dir(const char *p) {
    struct stat st;
    return p && stat(p, &st) == 0 && S_ISDIR(st.st_mode);
}

static int eh_arquivo(const char *p) {
    struct stat st;
    return p && stat(p, &st) == 0 && S_ISREG(st.st_mode);
}

static int eh_executavel(const char *p) {
    return p && access(p, X_OK) == 0;
}

static int nome_valido(const char *nome) {
    if (!nome || strncmp(nome, "conector-", 9) != 0 || strlen(nome) > 128) return 0;
    if (strstr(nome, "..")) return 0;
    for (const char *p = nome; *p; p++)
        if (!((*p >= 'a' && *p <= 'z') || (*p >= '0' && *p <= '9') || *p == '-')) return 0;
    return 1;
}

static int shell_quote(const char *s, char *out, size_t outsz) {
    size_t n = 0;
    if (n + 1 >= outsz) return -1;
    out[n++] = '\'';
    for (const char *p = s ? s : ""; *p; p++) {
        if (*p == '\'') {
            const char *q = "'\\''";
            for (int i = 0; q[i]; i++) {
                if (n + 1 >= outsz) return -1;
                out[n++] = q[i];
            }
        } else {
            if (n + 1 >= outsz) return -1;
            out[n++] = *p;
        }
    }
    if (n + 2 >= outsz) return -1;
    out[n++] = '\'';
    out[n] = '\0';
    return 0;
}

static char *ler_arquivo(const char *p) {
    FILE *fp = fopen(p, "rb");
    if (!fp) return NULL;
    if (fseek(fp, 0, SEEK_END) != 0) { fclose(fp); return NULL; }
    long t = ftell(fp);
    if (t < 0 || t > 20 * 1024 * 1024) { fclose(fp); return NULL; }
    rewind(fp);
    char *b = malloc((size_t)t + 1);
    if (!b) { fclose(fp); return NULL; }
    size_t n = fread(b, 1, (size_t)t, fp);
    fclose(fp);
    b[n] = '\0';
    return b;
}

/* Encontra o fim de um objeto/array JSON a partir da abertura. */
static const char *json_fim_composto(const char *abre, const char *limite) {
    char f = (*abre == '{') ? '}' : ']';
    int nivel = 0, em_str = 0, esc = 0;
    for (const char *p = abre; p < limite && *p; p++) {
        if (em_str) {
            if (esc) esc = 0;
            else if (*p == '\\') esc = 1;
            else if (*p == '"') em_str = 0;
            continue;
        }
        if (*p == '"') em_str = 1;
        else if (*p == *abre) nivel++;
        else if (*p == f) {
            nivel--;
            if (nivel == 0) return p + 1;
        }
    }
    return NULL;
}

/*
 * Traduz o manifesto do layout do kit para o operacional:
 * - catalogo-mensagens: origem siscconectores/web-api/... -> web-api/...
 * - remove entradas exemplo-uso/exemplo-consumidor (vivem no kit, e o
 *   empacotar-conector só aceita origens sob conectores/)
 */
static char *traduzir_manifesto(const char *json, const char *nome) {
    char alvo_origem[PATH_MAX];
    snprintf(alvo_origem, sizeof(alvo_origem), "\"origem\": \"siscconectores/web-api/catalogo-%s.json\"", nome);
    char nova_origem[PATH_MAX];
    snprintf(nova_origem, sizeof(nova_origem), "\"origem\": \"web-api/catalogo-%s.json\"", nome);

    size_t cap = strlen(json) + 256;
    char *out = malloc(cap);
    if (!out) return NULL;
    out[0] = '\0';

    /* Passo 1: troca a origem do catálogo. */
    const char *pos = strstr(json, alvo_origem);
    if (pos) {
        size_t a = (size_t)(pos - json);
        size_t b = strlen(json) - a - strlen(alvo_origem);
        if (a + strlen(nova_origem) + b + 1 > cap) {
            cap = a + strlen(nova_origem) + b + 1;
            char *nv = realloc(out, cap);
            if (!nv) { free(out); return NULL; }
            out = nv;
        }
        memcpy(out, json, a);
        memcpy(out + a, nova_origem, strlen(nova_origem));
        memcpy(out + a + strlen(nova_origem), pos + strlen(alvo_origem), b + 1);
    } else {
        snprintf(out, cap, "%s", json);
    }

    /* Passo 2: remove objetos com papel exemplo-uso/exemplo-consumidor. */
    for (;;) {
        const char *pu = strstr(out, "\"papel\": \"exemplo-uso\"");
        const char *pc = strstr(out, "\"papel\": \"exemplo-consumidor\"");
        const char *alvo = NULL;
        if (pu && pc) alvo = pu < pc ? pu : pc;
        else alvo = pu ? pu : pc;
        if (!alvo) break;

        /* Acha a abertura do objeto que contém o papel. */
        const char *ini = alvo;
        int nivel = 0;
        const char *p = alvo;
        /* volta até a linha com '{' balanceando chaves */
        while (p > out) {
            p--;
            if (*p == '}') nivel++;
            else if (*p == '{') {
                if (nivel == 0) { ini = p; break; }
                nivel--;
            }
        }
        if (p <= out && *p != '{') break;
        const char *fim = json_fim_composto(ini, out + strlen(out));
        if (!fim) break;
        /* Remove também vírgula adjacente para manter JSON válido. */
        const char *r_ini = ini;
        const char *r_fim = fim;
        const char *q = r_ini;
        while (q > out && isspace((unsigned char)*(q - 1))) q--;
        if (q > out && *(q - 1) == ',') r_ini = q - 1;
        else {
            q = r_fim;
            while (*q && isspace((unsigned char)*q)) q++;
            if (*q == ',') r_fim = q + 1;
        }
        size_t a = (size_t)(r_ini - out);
        size_t b = strlen(r_fim);
        memmove(out + a, r_fim, b + 1);
    }
    return out;
}

static void rm_rf(const char *dir) {
    char q[PATH_MAX * 4], cmd[PATH_MAX * 5];
    if (shell_quote(dir, q, sizeof(q)) == 0) {
        snprintf(cmd, sizeof(cmd), "rm -rf %s", q);
        system(cmd);
    }
}

/* Executa o handler com a mensagem e captura stdout; exige exit 0 e sucesso:true.
 * Se sandbox_bin for informado, executa isolado via sandbox-handler. */
static int teste_sandbox(const char *handler, const char *mensagem, const char *sandbox_bin, const char *sistema, const char *nome) {
    char qh[PATH_MAX * 4], qm[PATH_MAX * 4], cmd[PATH_MAX * 9];
    if (sandbox_bin) {
        char qs[PATH_MAX * 4], qsys[PATH_MAX * 4], qn[PATH_MAX * 4], qhd[PATH_MAX * 4];
        if (shell_quote(sandbox_bin, qs, sizeof(qs)) != 0) return -1;
        if (shell_quote(sistema, qsys, sizeof(qsys)) != 0) return -1;
        if (shell_quote(nome, qn, sizeof(qn)) != 0) return -1;
        if (shell_quote(mensagem, qm, sizeof(qm)) != 0) return -1;
        if (shell_quote(handler, qhd, sizeof(qhd)) != 0) return -1;
        snprintf(cmd, sizeof(cmd), "%s %s %s %s %s 2>/dev/null", qs, qsys, qn, qm, qhd);
    } else {
        if (shell_quote(handler, qh, sizeof(qh)) != 0) return -1;
        if (shell_quote(mensagem, qm, sizeof(qm)) != 0) return -1;
        snprintf(cmd, sizeof(cmd), "%s %s 2>/dev/null", qh, qm);
    }
    FILE *fp = popen(cmd, "r");
    if (!fp) return -1;
    char saida[65536];
    size_t n = fread(saida, 1, sizeof(saida) - 1, fp);
    saida[n] = '\0';
    int rc = pclose(fp);
    int exit_ok = WIFEXITED(rc) && WEXITSTATUS(rc) == 0;
    if (!exit_ok) {
        fprintf(stderr, "  [erro] sandbox: handler retornou codigo %d\n", WIFEXITED(rc) ? WEXITSTATUS(rc) : -1);
        return -1;
    }
    if (!strstr(saida, "\"sucesso\":true") && !strstr(saida, "\"sucesso\": true")) {
        fprintf(stderr, "  [erro] sandbox: resposta sem sucesso:true: %.300s\n", saida);
        return -1;
    }
    printf("  [ok] sandbox: handler executou com sucesso:true\n");
    return 0;
}

static int tem_chave(const char *json, const char *chave, const char *valor) {
    char pad[320];
    snprintf(pad, sizeof(pad), "\"%s\"", chave);
    const char *p = strstr(json, pad);
    if (!p) return 0;
    if (!valor) return 1;
    snprintf(pad, sizeof(pad), "\"%s\": \"%s\"", chave, valor);
    if (strstr(json, pad)) return 1;
    snprintf(pad, sizeof(pad), "\"%s\":\"%s\"", chave, valor);
    return strstr(json, pad) != NULL;
}

int main(int argc, char **argv) {
    if (restricao_verificar("instalar-conector") != 0) return 1;
    const char *nome = NULL;
    const char *para = "siscore";
    int sim = 0;
    for (int i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "--ajuda") || !strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) { uso(); return 0; }
        else if (!strncmp(argv[i], "--instalar-conector=", 20)) { if (argv[i][20]) nome = argv[i] + 20; }
        else if (!strncmp(argv[i], "--para=", 7)) { if (argv[i][7]) para = argv[i] + 7; }
        else if (!strcmp(argv[i], "--sim")) sim = 1;
        else if (argv[i][0] != '-' && !nome) nome = argv[i];
        else { fprintf(stderr, "instalar-conector: argumento inválido: %s\n", argv[i]); uso(); return 1; }
    }
    if (!nome) {
        fprintf(stderr, "instalar-conector: informe o conector: pp --instalar-conector=<nome>\n");
        uso();
        return 1;
    }
    if (!nome_valido(nome)) {
        fprintf(stderr, "instalar-conector: nome inválido: %s\n", nome);
        return 1;
    }
    if (strstr(para, "..") || strchr(para, '/')) {
        fprintf(stderr, "instalar-conector: sistema destino inválido: %s\n", para);
        return 1;
    }

    char raiz[PATH_MAX];
    if (raiz_sisc_por_executavel(raiz, sizeof(raiz)) != 0) {
        fprintf(stderr, "instalar-conector: raiz SISC não localizada.\n");
        return 1;
    }
    char testesis[PATH_MAX * 2], destino[PATH_MAX * 2];
    snprintf(testesis, sizeof(testesis), "%s/testesis", raiz);
    snprintf(destino, sizeof(destino), "%s/%s", raiz, para);
    if (!eh_dir(destino) || !eh_dir(testesis)) {
        fprintf(stderr, "instalar-conector: testesis ou destino ausentes (testesis=%s destino=%s).\n", testesis, destino);
        return 1;
    }

    char st_con[PATH_MAX * 3], st_cat[PATH_MAX * 3], st_man[PATH_MAX * 3];
    snprintf(st_con, sizeof(st_con), "%s/conectores/%s", testesis, nome);
    snprintf(st_cat, sizeof(st_cat), "%s/web-api/catalogo-%s.json", testesis, nome);
    snprintf(st_man, sizeof(st_man), "%s/%s.json", st_con, nome);

    char dt_con[PATH_MAX * 3], dt_cat[PATH_MAX * 3];
    snprintf(dt_con, sizeof(dt_con), "%s/conectores/%s", destino, nome);
    snprintf(dt_cat, sizeof(dt_cat), "%s/web-api/catalogo-%s.json", destino, nome);

    if (!eh_dir(st_con)) {
        fprintf(stderr, "instalar-conector: staging ausente em testesis: conectores/%s\n", nome);
        return 1;
    }
    if (eh_dir(dt_con) || access(dt_cat, F_OK) == 0) {
        fprintf(stderr, "instalar-conector: %s já instalado em %s; exclua antes (pp --excluir-conector=%s).\n", nome, para, nome);
        return 1;
    }

    printf("Instalando %s: testesis -> %s\n", nome, para);

    /* 1. Validação interna. */
    printf("[1/5] validação interna do pacote recebido...\n");
    char *man = ler_arquivo(st_man);
    if (!man) { fprintf(stderr, "  [erro] manifesto ausente: %s\n", st_man); return 1; }
    int ok = 1;
    if (!tem_chave(man, "nome", nome)) { fprintf(stderr, "  [erro] manifesto.nome diferente de %s\n", nome); ok = 0; }
    if (!tem_chave(man, "tipo", "conector")) { fprintf(stderr, "  [erro] manifesto.tipo deve ser conector\n"); ok = 0; }
    if (!strstr(man, "\"testeSandbox\"")) { fprintf(stderr, "  [erro] manifesto sem testeSandbox\n"); ok = 0; }
    else if (!strstr(man, "\"permitido\": true") && !strstr(man, "\"permitido\":true")) { fprintf(stderr, "  [erro] testeSandbox.permitido deve ser true\n"); ok = 0; }
    else if (!strstr(man, "\"semEfeitoReal\": true") && !strstr(man, "\"semEfeitoReal\":true")) { fprintf(stderr, "  [erro] testeSandbox.semEfeitoReal deve ser true\n"); ok = 0; }
    if (!strstr(man, "handlerLerMensagem")) { fprintf(stderr, "  [erro] manifesto sem handlerLerMensagem\n"); ok = 0; }
    if (!ok) { free(man); return 1; }

    char handler_rel[PATH_MAX], handler[PATH_MAX * 3], mensagem[PATH_MAX * 3];
    snprintf(handler_rel, sizeof(handler_rel), "conectores/%s/handlers/%s.php", nome, nome);
    /* localiza o handler declarado (qualquer extensão executável). */
    handler[0] = '\0';
    {
        char hdir[PATH_MAX * 3];
        snprintf(hdir, sizeof(hdir), "%s/handlers", st_con);
        DIR *d = opendir(hdir);
        if (d) {
            struct dirent *e;
            while ((e = readdir(d)) && !handler[0]) {
                if (e->d_name[0] == '.') continue;
                char cand[PATH_MAX * 4];
                snprintf(cand, sizeof(cand), "%s/%s", hdir, e->d_name);
                struct stat st;
                if (stat(cand, &st) == 0 && S_ISREG(st.st_mode) && access(cand, X_OK) == 0)
                    snprintf(handler, sizeof(handler), "%s", cand);
            }
            closedir(d);
        }
    }
    if (!handler[0]) { fprintf(stderr, "  [erro] nenhum handler executável em conectores/%s/handlers/\n", nome); free(man); return 1; }
    snprintf(mensagem, sizeof(mensagem), "%s/testes/mensagem-exemplo.json", st_con);
    if (access(mensagem, F_OK) != 0) { fprintf(stderr, "  [erro] mensagem de teste ausente: %s\n", mensagem); free(man); return 1; }
    char *cat = ler_arquivo(st_cat);
    if (!cat) { fprintf(stderr, "  [erro] catálogo modular ausente: %s\n", st_cat); free(man); return 1; }
    if (!strstr(cat, "\"mensagens\"")) { fprintf(stderr, "  [erro] catálogo sem bloco mensagens\n"); free(man); free(cat); return 1; }
    {
        char dest_esp[PATH_MAX];
        snprintf(dest_esp, sizeof(dest_esp), "conector__%s", nome);
        if (!strstr(cat, dest_esp)) { fprintf(stderr, "  [erro] catálogo sem destino %s\n", dest_esp); free(man); free(cat); return 1; }
    }
    free(cat);
    free(man);
    printf("  [ok] manifesto, handler executável, mensagem de teste e catálogo conferidos\n");

    /* 2. Teste sandbox. */
    printf("[2/5] teste sandbox do handler...\n");
    char sandbox_bin[PATH_MAX * 3];
    snprintf(sandbox_bin, sizeof(sandbox_bin), "%s/escuta/sandbox-handler", testesis);
    const char *sb = (eh_arquivo(sandbox_bin) && eh_executavel(sandbox_bin)) ? sandbox_bin : NULL;
    if (teste_sandbox(handler, mensagem, sb, testesis, nome) != 0) {
        fprintf(stderr, "instalar-conector: reprovado no sandbox; staging preservado em testesis.\n");
        return 1;
    }

    if (!sim) {
        char resp[16];
        printf("Instalar %s em %s e remover o staging de testesis? [s/N]: ", nome, para);
        fflush(stdout);
        if (!fgets(resp, sizeof(resp), stdin)) return 1;
        if (!(resp[0] == 's' || resp[0] == 'S' || resp[0] == 'y' || resp[0] == 'Y')) {
            printf("Instalação cancelada; staging preservado.\n");
            return 1;
        }
    }

    /* 3. Instala: copia staging -> destino com tradução do manifesto. */
    printf("[3/5] instalando em %s...\n", para);
    {
        char qst[PATH_MAX * 4], qdt[PATH_MAX * 4], cmd[PATH_MAX * 9];
        if (shell_quote(st_con, qst, sizeof(qst)) != 0 || shell_quote(dt_con, qdt, sizeof(qdt)) != 0) return 1;
        snprintf(cmd, sizeof(cmd), "mkdir -p %s && cp -r %s/. %s/", qdt, qst, qdt);
        if (system(cmd) != 0) { fprintf(stderr, "  [erro] falha ao copiar conectores/%s\n", nome); return 1; }
        printf("  [ok] conectores/%s instalado\n", nome);
    }
    /* traduz o manifesto instalado (kit -> operacional). */
    {
        char man_path[PATH_MAX * 4];
        snprintf(man_path, sizeof(man_path), "%s/%s.json", dt_con, nome);
        char *mj = ler_arquivo(man_path);
        if (!mj) { fprintf(stderr, "  [erro] manifesto instalado ilegível\n"); return 1; }
        char *tj = traduzir_manifesto(mj, nome);
        free(mj);
        if (!tj) { fprintf(stderr, "  [erro] falha ao traduzir manifesto\n"); return 1; }
        FILE *fp = fopen(man_path, "wb");
        if (!fp) { fprintf(stderr, "  [erro] falha ao gravar manifesto traduzido\n"); free(tj); return 1; }
        fwrite(tj, 1, strlen(tj), fp);
        fclose(fp);
        free(tj);
        printf("  [ok] manifesto traduzido para o layout operacional\n");
    }
    {
        char qst[PATH_MAX * 4], qdt[PATH_MAX * 4], cmd[PATH_MAX * 9];
        if (shell_quote(st_cat, qst, sizeof(qst)) != 0 || shell_quote(dt_cat, qdt, sizeof(qdt)) != 0) return 1;
        snprintf(cmd, sizeof(cmd), "cp %s %s", qst, qdt);
        if (system(cmd) != 0) { fprintf(stderr, "  [erro] falha ao copiar catálogo modular\n"); return 1; }
        printf("  [ok] web-api/catalogo-%s.json instalado\n", nome);
    }

    /* 4. Regenera o agregado e verifica. */
    printf("[4/5] regenerando catálogo agregado e verificando...\n");
    {
        char qdt[PATH_MAX * 4], cmd[PATH_MAX * 8];
        if (shell_quote(destino, qdt, sizeof(qdt)) != 0) return 1;
        snprintf(cmd, sizeof(cmd), "cd %s && ../pp --atualizar-catalogo-mensagens >/dev/null 2>&1", qdt);
        if (system(cmd) != 0) fprintf(stderr, "  [aviso] falha ao regenerar agregado; rode pp --atualizar-catalogo-mensagens\n");
        else printf("  [ok] catálogo agregado regenerado\n");
    }
    {
        char dt_handler[PATH_MAX * 4], dt_msg[PATH_MAX * 4];
        snprintf(dt_handler, sizeof(dt_handler), "%s/handlers/%s.php", dt_con, nome);
        /* handler pode ter outra extensão; localiza o executável instalado. */
        char hdir[PATH_MAX * 4];
        snprintf(hdir, sizeof(hdir), "%s/handlers", dt_con);
        dt_handler[0] = '\0';
        DIR *d = opendir(hdir);
        if (d) {
            struct dirent *e;
            while ((e = readdir(d)) && !dt_handler[0]) {
                if (e->d_name[0] == '.') continue;
                char cand[PATH_MAX * 5];
                snprintf(cand, sizeof(cand), "%s/%s", hdir, e->d_name);
                if (eh_arquivo(cand) && eh_executavel(cand))
                    snprintf(dt_handler, sizeof(dt_handler), "%s", cand);
            }
            closedir(d);
        }
        snprintf(dt_msg, sizeof(dt_msg), "%s/testes/mensagem-exemplo.json", dt_con);
        if (!dt_handler[0] || teste_sandbox(dt_handler, dt_msg, NULL, destino, nome) != 0) {
            fprintf(stderr, "instalar-conector: falha na verificação pós-instalação; staging preservado.\n");
            return 1;
        }
    }

    /* 5. Move: remove o staging de testesis. */
    printf("[5/5] removendo staging de testesis (instalação move, não copia)...\n");
    rm_rf(st_con);
    if (unlink(st_cat) != 0 && errno != ENOENT)
        fprintf(stderr, "  [aviso] falha ao remover %s: %s\n", st_cat, strerror(errno));
    else printf("  [ok] staging removido de testesis\n");

    printf("Instalação concluída: %s operacional em %s.\n", nome, para);
    return 0;
}
