#include "conectores-operacional.h"

#define GC_SANDBOX_ORIGEM "sistema__sandbox-gitconectores"

typedef struct {
    char url[PATH_MAX];
    char token[1024];
} SandboxCred;

static void uso(void) {
    printf("Uso:\n");
    printf("  ./testar-sandbox [--retestar] [--destino=/var/www/html/sisc/testesis] [--pacote=/caminho/pacote.tar.gz]\n\n");
    printf("Instala o pacote no testesis, executa dry-run + sandbox e emite <pacote>.selo-sandbox.json assinado.\n");
    printf("O manifesto do conector deve declarar testeSandbox.permitido=true e testeSandbox.semEfeitoReal=true.\n");
}

static void trim_inplace(char *s) {
    if (!s) return;
    char *p = s;
    while (*p && isspace((unsigned char)*p)) p++;
    if (p != s) memmove(s, p, strlen(p) + 1);
    char *e = s + strlen(s);
    while (e > s && isspace((unsigned char)e[-1])) *--e = '\0';
}

static void lower_inplace(char *s) {
    for (; s && *s; s++) *s = (char)tolower((unsigned char)*s);
}

static int listar_selados_validacao(char arr[][PATH_MAX], int max) {
    DIR *d = opendir(GC_RECEBIDOS); if (!d) return 0;
    int n = 0; struct dirent *e;
    while ((e = readdir(d)) && n < max) {
        if (!ends_with(e->d_name, ".selo-validacao.json")) continue;
        char pacote[PATH_MAX]; snprintf(pacote, sizeof(pacote), "%s/%s", GC_RECEBIDOS, e->d_name);
        pacote[strlen(pacote) - strlen(".selo-validacao.json")] = '\0';
        if (is_file_p(pacote)) snprintf(arr[n++], PATH_MAX, "%s", pacote);
    }
    closedir(d);
    for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) if (strcmp(arr[i], arr[j]) > 0) { char t[PATH_MAX]; strcpy(t, arr[i]); strcpy(arr[i], arr[j]); strcpy(arr[j], t); }
    return n;
}

static int arquivo_contem_literal(const char *path, const char *lit) {
    if (!path || !lit || !*lit) return 0;
    FILE *fp = fopen(path, "rb");
    if (!fp) return -1;
    size_t n = strlen(lit), pos = 0;
    int c;
    while ((c = fgetc(fp)) != EOF) {
        if ((unsigned char)c == (unsigned char)lit[pos]) {
            pos++;
            if (pos == n) { fclose(fp); return 1; }
        } else {
            pos = ((unsigned char)c == (unsigned char)lit[0]) ? 1 : 0;
        }
    }
    fclose(fp);
    return 0;
}

static int preflight_sandbox_runtime(const char *destino, char *log, size_t logsz) {
    int erros = 0;
    const char *desativado = getenv("SISC_SANDBOX_DESATIVADO");
    if (desativado && *desativado) {
        sb_add(log, logsz, "SISC_SANDBOX_DESATIVADO esta definido; sandbox automatico bloqueado para nao emitir selo sem isolamento\n");
        erros++;
    }

    char sandbox[PATH_MAX], runtime[PATH_MAX], fonte[PATH_MAX];
    snprintf(sandbox, sizeof(sandbox), "%s/escuta/sandbox-handler", destino);
    snprintf(runtime, sizeof(runtime), "%s/escuta/runtime-conector", destino);
    snprintf(fonte, sizeof(fonte), "%s/escuta/runtime_comum.c", destino);

    if (!is_exec_p(sandbox)) {
        sb_add(log, logsz, "sandbox-handler ausente ou sem execucao: %s\n", sandbox);
        erros++;
    }
    if (!is_exec_p(runtime)) {
        sb_add(log, logsz, "runtime-conector ausente ou sem execucao: %s\n", runtime);
        erros++;
    }

    int bin_sandbox = arquivo_contem_literal(runtime, "sandbox-handler") == 1;
    int bin_flag = arquivo_contem_literal(runtime, "SISC_SANDBOX_DESATIVADO") == 1;
    int bin_path = arquivo_contem_literal(runtime, "handler de conector deve ser caminho executavel direto") == 1;
    if (!bin_sandbox || !bin_flag || !bin_path) {
        sb_add(log, logsz, "runtime-conector nao parece compilado com o sandbox novo (sandbox=%d flag=%d pathcheck=%d): %s\n", bin_sandbox, bin_flag, bin_path, runtime);
        erros++;
    }

    if (is_file_p(fonte)) {
        int src_sandbox = arquivo_contem_literal(fonte, "sandbox-handler") == 1;
        int src_flag = arquivo_contem_literal(fonte, "SISC_SANDBOX_DESATIVADO") == 1;
        int src_path = arquivo_contem_literal(fonte, "preparar_handler_conector") == 1;
        if (!src_sandbox || !src_flag || !src_path) {
            sb_add(log, logsz, "runtime_comum.c do destino nao contem as guardas esperadas do sandbox novo (sandbox=%d flag=%d pathcheck=%d): %s\n", src_sandbox, src_flag, src_path, fonte);
            erros++;
        }
    } else {
        sb_add(log, logsz, "aviso: runtime_comum.c nao encontrado no destino; validacao ficou restrita ao binario runtime-conector\n");
    }

    return erros ? -1 : 0;
}

static const char *skip_json_ws(const char *p);
static const char *json_value_end(const char *p);

static int manifesto_declara_sandbox_seguro(const char *manifesto, char *log, size_t logsz) {
    const char *p = strstr(manifesto ? manifesto : "", "\"testeSandbox\"");
    if (!p) { sb_add(log, logsz, "manifesto sem testeSandbox; sandbox automatico bloqueado\n"); return 0; }
    const char *colon = strchr(p, ':');
    if (!colon) { sb_add(log, logsz, "testeSandbox invalido no manifesto\n"); return 0; }
    const char *obj = skip_json_ws(colon + 1);
    if (!obj || *obj != '{') { sb_add(log, logsz, "testeSandbox deve ser objeto JSON\n"); return 0; }
    const char *end = json_value_end(obj);
    if (!end || end <= obj) { sb_add(log, logsz, "testeSandbox nao fecha corretamente\n"); return 0; }
    char trecho[8192];
    size_t n = (size_t)(end - obj); if (n > sizeof(trecho) - 1) n = sizeof(trecho) - 1;
    memcpy(trecho, obj, n); trecho[n] = '\0';
    int permitido = contains_key_bool_true(trecho, "permitido");
    int sem_efeito = contains_key_bool_true(trecho, "semEfeitoReal") || contains_key_bool_true(trecho, "sem_efeito_real") || contains_key_bool_true(trecho, "sem-efeito-real");
    if (!permitido) sb_add(log, logsz, "testeSandbox.permitido deve ser true\n");
    if (!sem_efeito) sb_add(log, logsz, "testeSandbox.semEfeitoReal deve ser true\n");
    return permitido && sem_efeito;
}

static const char *skip_json_ws(const char *p) {
    while (p && *p && isspace((unsigned char)*p)) p++;
    return p;
}

static const char *json_value_end(const char *p) {
    p = skip_json_ws(p);
    if (!p || !*p) return NULL;
    if (*p == '{' || *p == '[') {
        char open = *p, close = (*p == '{') ? '}' : ']';
        int depth = 0, str = 0, esc = 0;
        for (const char *q = p; *q; q++) {
            if (esc) { esc = 0; continue; }
            if (str) { if (*q == '\\') esc = 1; else if (*q == '"') str = 0; continue; }
            if (*q == '"') str = 1;
            else if (*q == open) depth++;
            else if (*q == close) { depth--; if (depth == 0) return q + 1; }
        }
        return NULL;
    }
    if (*p == '"') {
        int esc = 0;
        for (const char *q = p + 1; *q; q++) {
            if (esc) esc = 0;
            else if (*q == '\\') esc = 1;
            else if (*q == '"') return q + 1;
        }
        return NULL;
    }
    const char *q = p;
    while (*q && *q != ',' && *q != '}' && *q != ']' && *q != '\n' && *q != '\r') q++;
    while (q > p && isspace((unsigned char)q[-1])) q--;
    return q > p ? q : NULL;
}

static int extrair_payload_dados(const char *teste, char **out, char *log, size_t logsz) {
    const char *p = strstr(teste ? teste : "", "\"payload\"");
    if (p) p = strstr(p, "\"dados\"");
    if (!p) p = strstr(teste ? teste : "", "\"dados\"");
    if (!p) { sb_add(log, logsz, "mensagem de teste sem payload.dados\n"); return -1; }
    p = strchr(p, ':');
    if (!p) { sb_add(log, logsz, "payload.dados invalido\n"); return -1; }
    p = skip_json_ws(p + 1);
    const char *end = json_value_end(p);
    if (!end || end <= p) { sb_add(log, logsz, "nao foi possivel extrair JSON de payload.dados\n"); return -1; }
    size_t n = (size_t)(end - p);
    char *v = malloc(n + 1); if (!v) return -1;
    memcpy(v, p, n); v[n] = '\0';
    *out = v;
    return 0;
}

static int parse_token_file(const char *path, SandboxCred *cred) {
    char *txt = read_file_alloc(path, NULL); if (!txt) return -1;
    char *save = NULL;
    for (char *line = strtok_r(txt, "\r\n", &save); line; line = strtok_r(NULL, "\r\n", &save)) {
        trim_inplace(line);
        if (line[0] == '\0' || line[0] == '#') continue;
        char key[128] = "", val[1024] = "";
        char *sep = strchr(line, '=');
        if (!sep) sep = strchr(line, ':');
        if (sep) {
            size_t kn = (size_t)(sep - line); if (kn >= sizeof(key)) kn = sizeof(key) - 1;
            memcpy(key, line, kn); key[kn] = '\0'; trim_inplace(key); lower_inplace(key);
            snprintf(val, sizeof(val), "%s", sep + 1); trim_inplace(val);
        } else {
            snprintf(val, sizeof(val), "%s", line); trim_inplace(val);
        }
        if ((strcmp(key, "url") == 0 || strcmp(key, "api") == 0 || strcmp(key, "api_url") == 0 || strcmp(key, "sisc_api_url") == 0) && cred->url[0] == '\0') {
            snprintf(cred->url, sizeof(cred->url), "%s", val);
        } else if (cred->token[0] == '\0' && strlen(val) >= 16
                   && (strcmp(key, "token") == 0 || strcmp(key, "sisc_api_token") == 0 || key[0] == '\0'
                       || (strcmp(key, "origem") != 0 && strcmp(key, "sisc_api_origem") != 0))) {
            snprintf(cred->token, sizeof(cred->token), "%s", val);
        }
    }
    free(txt);
    return cred->token[0] ? 0 : -1;
}

static int ler_credenciais_sandbox(const char *destino, SandboxCred *cred, char *log, size_t logsz) {
    memset(cred, 0, sizeof(*cred));
    const char *env_url = getenv("SISC_SANDBOX_API_URL");
    const char *env_tok = getenv("SISC_SANDBOX_API_TOKEN");
    if (env_url && *env_url) snprintf(cred->url, sizeof(cred->url), "%s", env_url);
    if (env_tok && strlen(env_tok) >= 16) snprintf(cred->token, sizeof(cred->token), "%s", env_tok);

    char tdir[PATH_MAX]; snprintf(tdir, sizeof(tdir), "%s/token-externo", destino);
    if ((!cred->token[0] || !cred->url[0]) && is_dir_p(tdir)) {
        DIR *d = opendir(tdir);
        if (d) {
            char arquivos[256][PATH_MAX]; int n = 0; struct dirent *e;
            while ((e = readdir(d)) && n < 256) {
                if (e->d_name[0] == '.' || !ends_with(e->d_name, ".txt")) continue;
                snprintf(arquivos[n++], PATH_MAX, "%s/%s", tdir, e->d_name);
            }
            closedir(d);
            for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) if (strcmp(arquivos[i], arquivos[j]) > 0) { char tmp[PATH_MAX]; strcpy(tmp, arquivos[i]); strcpy(arquivos[i], arquivos[j]); strcpy(arquivos[j], tmp); }
            for (int i = 0; i < n && (!cred->token[0] || !cred->url[0]); i++) parse_token_file(arquivos[i], cred);
        }
    }

    if (!cred->url[0]) {
        const char *base = strrchr(destino, '/'); base = base ? base + 1 : destino;
        snprintf(cred->url, sizeof(cred->url), "https://costarrear.com/sisc/%s/conexao-externo/api.php", base && *base ? base : "testesis");
    }
    if (!cred->token[0]) { sb_add(log, logsz, "token de sandbox ausente; configure SISC_SANDBOX_API_TOKEN ou %s/*.txt\n", tdir); return -1; }
    return 0;
}

static int write_private_file(const char *path, const char *txt) {
    char dir[PATH_MAX]; dirname_of(path, dir, sizeof(dir));
    if (mkdir_p(dir, 0770) != 0) return -1;
    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
    if (fd < 0) return -1;
    size_t len = strlen(txt ? txt : "");
    ssize_t wr = write(fd, txt ? txt : "", len);
    int ok = (wr == (ssize_t)len && close(fd) == 0) ? 0 : -1;
    chmod(path, 0600);
    return ok;
}

static int curl_post_json(const SandboxCred *cred, const char *body, const char *etiqueta, char **resp_out, char *log, size_t logsz) {
    if (resp_out) *resp_out = NULL;
    char req[PATH_MAX], resp[PATH_MAX], cfg[PATH_MAX];
    snprintf(req, sizeof(req), GC_BASE "/tmp/sandbox-%ld-%ld-%s.req.json", (long)time(NULL), (long)getpid(), etiqueta);
    snprintf(resp, sizeof(resp), GC_BASE "/tmp/sandbox-%ld-%ld-%s.resp.json", (long)time(NULL), (long)getpid(), etiqueta);
    snprintf(cfg, sizeof(cfg), GC_BASE "/tmp/sandbox-%ld-%ld-%s.curl", (long)time(NULL), (long)getpid(), etiqueta);
    if (write_private_file(req, body) != 0) { sb_add(log, logsz, "falha gravando requisicao temporaria\n"); return -1; }
    char conf[8192];
    snprintf(conf, sizeof(conf),
        "silent\n"
        "show-error\n"
        "location\n"
        "max-time = 90\n"
        "request = \"POST\"\n"
        "url = \"%s\"\n"
        "header = \"Authorization: Bearer %s\"\n"
        "header = \"Content-Type: application/json\"\n"
        "header = \"Accept: application/json\"\n"
        "data-binary = \"@%s\"\n"
        "output = \"%s\"\n"
        "write-out = \"HTTP_STATUS:%%{http_code}\"\n",
        cred->url, cred->token, req, resp);
    if (write_private_file(cfg, conf) != 0) { unlink(req); sb_add(log, logsz, "falha gravando configuracao curl\n"); return -1; }
    char qcfg[PATH_MAX * 2], cmd[PATH_MAX * 3], cap[65536] = "";
    shell_quote(cfg, qcfg, sizeof(qcfg));
    snprintf(cmd, sizeof(cmd), "curl -sS -K %s 2>&1", qcfg);
    int rc = capture_cmd(cmd, cap, sizeof(cap));
    unlink(cfg); unlink(req);
    int http = 0;
    char *hs = strstr(cap, "HTTP_STATUS:");
    if (hs) http = atoi(hs + strlen("HTTP_STATUS:"));
    char *r = read_file_alloc(resp, NULL);
    unlink(resp);
    if (rc != 0 || http < 200 || http >= 300 || !r) {
        sb_add(log, logsz, "curl/API falhou em %s: rc=%d http=%d saida=%s\n", etiqueta, rc, http, cap);
        if (r) { sb_add(log, logsz, "resposta API: %.2000s\n", r); free(r); }
        return -1;
    }
    if (strstr(r, "\"status\": \"erro\"") || strstr(r, "\"status\":\"erro\"")) {
        sb_add(log, logsz, "API retornou erro em %s: %.2000s\n", etiqueta, r);
        free(r); return -1;
    }
    if (resp_out) *resp_out = r; else free(r);
    return 0;
}

static char *montar_requisicao(const char *idmensagem, const char *dados, int executar, const char *processo, const char *mensagem, const char *idempotencia) {
    size_t tam = strlen(dados ? dados : "{}") + 4096;
    char *body = malloc(tam); if (!body) return NULL;
    snprintf(body, tam,
        "{\n"
        "  \"idmensagem\": \"%s\",\n"
        "  \"origem\": \"%s\",\n"
        "  \"processoId\": \"%s\",\n"
        "  \"mensagemId\": \"%s\",\n"
        "  \"idempotencia\": \"%s\",\n"
        "  \"dados\": %s,\n"
        "  \"%s\": true\n"
        "}\n",
        idmensagem, GC_SANDBOX_ORIGEM, processo, mensagem, idempotencia, dados && *dados ? dados : "{}", executar ? "executar" : "dryRun");
    return body;
}

static int instalar_no_sandbox(const char *pacote, const char *destino, char *log, size_t logsz) {
    char qbase[PATH_MAX * 2], qdest[PATH_MAX * 2], qpac[PATH_MAX * 2], cmd[PATH_MAX * 6], out[65536] = "";
    shell_quote(GC_BASE, qbase, sizeof(qbase)); shell_quote(destino, qdest, sizeof(qdest)); shell_quote(pacote, qpac, sizeof(qpac));
    snprintf(cmd, sizeof(cmd), "cd %s && ./instalar-aprovados --forcar --destino=%s --pacote=%s 2>&1", qbase, qdest, qpac);
    int rc = capture_cmd(cmd, out, sizeof(out));
    sb_add(log, logsz, "instalar-aprovados sandbox rc=%d\n%.4000s\n", rc, out);
    return rc == 0 ? 0 : -1;
}

static int aguardar_conclusao(const char *destino, const char *mensagem_id, char *log, size_t logsz) {
    char concl[PATH_MAX], term[PATH_MAX], escuta[PATH_MAX];
    snprintf(concl, sizeof(concl), "%s/espaco/estado/concluidas/%s.idx", destino, mensagem_id);
    snprintf(term, sizeof(term), "%s/espaco/estado/terminais/%s.idx", destino, mensagem_id);
    snprintf(escuta, sizeof(escuta), "%s/escuta/escuta", destino);
    for (int i = 0; i < 10; i++) {
        if (is_file_p(concl)) { sb_add(log, logsz, "mensagem sandbox concluida: %s\n", mensagem_id); return 0; }
        if (is_file_p(term)) { sb_add(log, logsz, "mensagem sandbox foi para terminal/quarentena: %s\n", mensagem_id); return -1; }
        if (is_exec_p(escuta)) {
            char qdest[PATH_MAX * 2], qesc[PATH_MAX * 2], cmd[PATH_MAX * 4], out[32768] = "";
            shell_quote(destino, qdest, sizeof(qdest)); shell_quote(escuta, qesc, sizeof(qesc));
            snprintf(cmd, sizeof(cmd), "cd %s && %s --once 2>&1", qdest, qesc);
            int rc = capture_cmd(cmd, out, sizeof(out));
            sb_add(log, logsz, "escuta --once tentativa=%d rc=%d\n%.2000s\n", i + 1, rc, out);
        }
        sleep(1);
    }
    sb_add(log, logsz, "mensagem sandbox nao concluiu apos tentativas: %s\n", mensagem_id);
    return -1;
}

static int testar_um(const char *pacote_arg, const char *destino, int retestar, char *nome, size_t ntam, char *log, size_t logsz) {
    char pacote[PATH_MAX];
    if (canonical_path(pacote_arg, pacote, sizeof(pacote)) != 0 || !is_file_p(pacote)) { sb_add(log, logsz, "pacote invalido/ausente\n"); return -1; }

    char nome_selo[256] = "";
    if (!retestar && gc_validar_selo_pacote(pacote, "selo-sandbox", nome_selo, sizeof(nome_selo), log, logsz) == 0) {
        snprintf(nome, ntam, "%s", nome_selo);
        sb_add(log, logsz, "selo-sandbox ja valido; use --retestar para emitir novamente\n");
        return 1;
    }
    log[0] = '\0'; /* descarta mensagens esperadas de selo-sandbox ausente antes de retestar */
    if (gc_validar_selo_pacote(pacote, "selo-validacao", nome_selo, sizeof(nome_selo), log, logsz) != 0) {
        sb_add(log, logsz, "sandbox bloqueado: falta selo-validacao valido\n"); return -1;
    }

    char tmp[PATH_MAX]; snprintf(tmp, sizeof(tmp), GC_BASE "/tmp/sandbox-c-%ld-%ld", (long)time(NULL), (long)getpid());
    int rc = -1;
    char *man = NULL, *teste = NULL, *dados = NULL;
    if (extrair_pacote(pacote, tmp, log, logsz) != 0) goto fim;
    char root[PATH_MAX];
    if (find_project_root(tmp, root, sizeof(root)) != 0) { sb_add(log, logsz, "raiz do projeto nao encontrada no pacote\n"); goto fim; }
    if (validar_projeto(root, nome, ntam, log, logsz) != 0) { sb_add(log, logsz, "revalidacao do projeto falhou antes do sandbox\n"); goto fim; }
    if (strcmp(nome, nome_selo) != 0) { sb_add(log, logsz, "nome do pacote nao confere com selo-validacao\n"); goto fim; }

    char manifesto_path[PATH_MAX]; snprintf(manifesto_path, sizeof(manifesto_path), "%s/conectores/%s/%s.json", root, nome, nome);
    man = read_file_alloc(manifesto_path, NULL); if (!man) { sb_add(log, logsz, "manifesto indisponivel no sandbox\n"); goto fim; }
    if (!manifesto_declara_sandbox_seguro(man, log, logsz)) goto fim;

    char teste_path[PATH_MAX]; snprintf(teste_path, sizeof(teste_path), "%s/conectores/%s/testes/mensagem-exemplo.json", root, nome);
    teste = read_file_alloc(teste_path, NULL); if (!teste) { sb_add(log, logsz, "mensagem de teste ausente em conectores/%s/testes/mensagem-exemplo.json\n", nome); goto fim; }
    char idmensagem[160] = "";
    if (!json_get_string(teste, "idmensagem", idmensagem, sizeof(idmensagem)) || idmensagem[0] == '\0') { sb_add(log, logsz, "idmensagem ausente na mensagem de teste\n"); goto fim; }
    if (extrair_payload_dados(teste, &dados, log, logsz) != 0) goto fim;

    if (instalar_no_sandbox(pacote, destino, log, logsz) != 0) goto fim;
    SandboxCred cred;
    if (ler_credenciais_sandbox(destino, &cred, log, logsz) != 0) goto fim;

    char sha[128] = "", sh[17] = ""; sha256_file(pacote, sha, sizeof(sha)); memcpy(sh, sha, 16); sh[16] = '\0';
    long agora = (long)time(NULL), pid = (long)getpid();
    char proc_dry[128], msg_dry[128], idem_dry[192], proc_exec[128], msg_exec[128], idem_exec[192];
    snprintf(proc_dry, sizeof(proc_dry), "proc-sandbox-dry-%s-%ld-%ld", sh, agora, pid);
    snprintf(msg_dry, sizeof(msg_dry), "msg-sandbox-dry-%s-%ld-%ld", sh, agora, pid);
    snprintf(idem_dry, sizeof(idem_dry), "sandbox-dry-%s-%ld-%ld", sh, agora, pid);
    snprintf(proc_exec, sizeof(proc_exec), "proc-sandbox-exec-%s-%ld-%ld", sh, agora, pid);
    snprintf(msg_exec, sizeof(msg_exec), "msg-sandbox-exec-%s-%ld-%ld", sh, agora, pid);
    snprintf(idem_exec, sizeof(idem_exec), "sandbox-exec-%s-%ld-%ld", sh, agora, pid);

    char *body = montar_requisicao(idmensagem, dados, 0, proc_dry, msg_dry, idem_dry);
    if (!body) goto fim;
    char *resp = NULL;
    if (curl_post_json(&cred, body, "dryrun", &resp, log, logsz) != 0) { free(body); goto fim; }
    free(body);
    char alvo[320]; snprintf(alvo, sizeof(alvo), "conector__%s", nome);
    if (!strstr(resp, "\"_protocolo\"") || !strstr(resp, alvo)) { sb_add(log, logsz, "dry-run nao retornou envelope esperado para %s\n", alvo); free(resp); goto fim; }
    sb_add(log, logsz, "dry-run sandbox aprovado para idmensagem=%s\n", idmensagem);
    free(resp);

    body = montar_requisicao(idmensagem, dados, 1, proc_exec, msg_exec, idem_exec);
    if (!body) goto fim;
    resp = NULL;
    if (curl_post_json(&cred, body, "execucao", &resp, log, logsz) != 0) { free(body); goto fim; }
    free(body);
    if (!strstr(resp, "\"status\": \"publicada\"") && !strstr(resp, "\"status\":\"publicada\"")) { sb_add(log, logsz, "execucao sandbox nao publicou mensagem: %.2000s\n", resp); free(resp); goto fim; }
    if (strstr(resp, "\"_execucao\"") && !strstr(resp, "\"ok\": true") && !strstr(resp, "\"ok\":true")) { sb_add(log, logsz, "execucao sandbox retornou _execucao.ok falso: %.2000s\n", resp); free(resp); goto fim; }
    free(resp);

    if (aguardar_conclusao(destino, msg_exec, log, logsz) != 0) goto fim;

    char resultado[256]; snprintf(resultado, sizeof(resultado), "dry-run+execucao:%s", msg_exec);
    if (gc_emitir_selo_pacote(pacote, "selo-sandbox", nome, "testesis", resultado, "testar-sandbox.c", log, logsz) != 0) goto fim;
    rc = 0;

fim:
    if (man) free(man);
    if (teste) free(teste);
    if (dados) free(dados);
    rm_rf(tmp);
    return rc;
}

static void escrever_falha_sandbox(const char *pacote_arg, const char *nome, const char *log) {
    char pacote[PATH_MAX]; if (canonical_path(pacote_arg, pacote, sizeof(pacote)) != 0) snprintf(pacote, sizeof(pacote), "%s", pacote_arg);
    char marca[PATH_MAX], sha[128] = ""; marker_path(pacote, "falha-sandbox", marca, sizeof(marca)); sha256_file(pacote, sha, sizeof(sha));
    char epac[PATH_MAX * 2], enome[512], esha[160]; gc_json_escape_copy(pacote, epac, sizeof(epac)); gc_json_escape_copy(nome ? nome : "", enome, sizeof(enome)); gc_json_escape_copy(sha, esha, sizeof(esha));
    char json[16384];
    snprintf(json, sizeof(json), "{\n  \"status\": \"falha-sandbox\",\n  \"pacote\": \"%s\",\n  \"sha256\": \"%s\",\n  \"conector\": \"%s\",\n  \"testadoEm\": %ld,\n  \"testador\": \"testar-sandbox.c\",\n  \"log\": \"ver arquivo .log correspondente\"\n}\n", epac, esha, enome, (long)time(NULL));
    write_text_file(marca, json, 0664);
    char lp[PATH_MAX]; snprintf(lp, sizeof(lp), "%s.log", marca); write_text_file(lp, log ? log : "", 0664);
}

int main(int argc, char **argv) {
    const char *destino = GC_TESTESIS_PADRAO, *somente = NULL;
    int retestar = 0;
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--retestar") == 0) retestar = 1;
        else if (starts_with(argv[i], "--destino=")) destino = argv[i] + 10;
        else if (starts_with(argv[i], "--pacote=")) somente = argv[i] + 9;
        else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--ajuda") == 0) { uso(); return 0; }
        else { fprintf(stderr, "argumento invalido: %s\n", argv[i]); uso(); return 2; }
    }

    mkdir_p(GC_RECEBIDOS, 0777); chmod(GC_RECEBIDOS, 01777);
    mkdir_p(GC_BASE "/tmp", 0770); chmod(GC_BASE "/tmp", 0770);

    char preflight[8192] = "";
    if (preflight_sandbox_runtime(destino, preflight, sizeof(preflight)) != 0) {
        fprintf(stderr, "[ERRO-SANDBOX-PREFLIGHT] destino=%s\n%s", destino, preflight);
        return 78;
    }

    static char pacotes[4096][PATH_MAX]; int total = 0;
    if (somente) snprintf(pacotes[total++], PATH_MAX, "%s", somente);
    else total = listar_selados_validacao(pacotes, 4096);
    if (total == 0) { printf("Nenhum pacote com selo-validacao encontrado em %s\n", GC_RECEBIDOS); return 0; }

    int okn = 0, errn = 0, ign = 0;
    for (int i = 0; i < total; i++) {
        char nome[256] = "", log[131072] = "";
        int rc = testar_um(pacotes[i], destino, retestar, nome, sizeof(nome), log, sizeof(log));
        if (rc == 0) { printf("[SANDBOX+SELO] %s => %s\n", pacotes[i], nome); okn++; }
        else if (rc == 1) { printf("[IGNORADO] %s ja possui selo-sandbox valido\n", pacotes[i]); ign++; }
        else { printf("[ERRO-SANDBOX] %s => %s\n", pacotes[i], log[0] ? log : "erro"); escrever_falha_sandbox(pacotes[i], nome, log); errn++; }
    }
    printf("\nResumo sandbox: total=%d aprovados=%d erros=%d ignorados=%d destino=%s\n", total, okn, errn, ign, destino);
    return errn > 0 ? 1 : 0;
}
