#define _GNU_SOURCE

#include "runtime_comum.h"

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#ifndef PATH_MAX
#define PATH_MAX 4096
#endif

#define PROTO_NOME "siscore-protocolo-objetos"
#define PROTO_VERSAO 1
#define MAX_ID 128
#define MAX_ENTIDADE 160
#define MAX_ARQ_BYTES (10 * 1024 * 1024)

typedef enum { J_NULL, J_BOOL, J_NUMBER, J_STRING, J_ARRAY, J_OBJECT } JType;

typedef struct Json Json;
typedef struct { char *key; Json *value; } Pair;

struct Json {
    JType type;
    union {
        bool b;
        double n;
        char *s;
        struct { Json **items; size_t len; } array;
        struct { Pair *pairs; size_t len; } object;
    } u;
};

typedef struct {
    const char *p;
    const char *end;
    char erro[256];
} Parser;

typedef struct {
    char processo_id[MAX_ID + 1];
    char mensagem_id[MAX_ID + 1];
    char origem[MAX_ENTIDADE + 1];
    char destino[MAX_ENTIDADE + 1];
    char ator_id[MAX_ID + 1];
    char resposta_a[MAX_ID + 1];
    char tipo_msg[32];
} Protocolo;

static void die_msg(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fputc('\n', stderr);
}

static void fmt_checked(char *out, size_t outsz, const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    int n = vsnprintf(out, outsz, fmt, ap);
    va_end(ap);
    if (n < 0 || (size_t)n >= outsz) {
        die_msg("ERRO: caminho/texto excede limite interno");
        exit(70);
    }
}

static void path_join(char *out, size_t outsz, const char *a, const char *b) {
    fmt_checked(out, outsz, "%s/%s", a, b);
}

static int raiz_sisc_de_base(const char *base, char *out, size_t outsz) {
    if (!base || !*base || !out || outsz == 0) return -1;
    char tmp[PATH_MAX];
    if (!realpath(base, tmp)) fmt_checked(tmp, sizeof tmp, "%s", base);
    char *slash = strrchr(tmp, '/');
    if (!slash || slash == tmp) return -1;
    *slash = '\0';
    fmt_checked(out, outsz, "%s", tmp);
    return 0;
}

static bool is_regular_file(const char *path) {
    struct stat st;
    return stat(path, &st) == 0 && S_ISREG(st.st_mode);
}

static bool mkdir_p(const char *path) {
    char tmp[PATH_MAX];
    size_t len = strlen(path);
    if (len == 0 || len >= sizeof(tmp)) return false;
    memcpy(tmp, path, len + 1);
    if (tmp[len - 1] == '/') tmp[len - 1] = 0;
    for (char *p = tmp + 1; *p; p++) {
        if (*p == '/') {
            *p = 0;
            if (mkdir(tmp, 02775) == 0) {
                if (chmod(tmp, 02775) != 0 && errno != EPERM && errno != EACCES) return false;
            } else if (errno != EEXIST) return false;
            *p = '/';
        }
    }
    if (mkdir(tmp, 02775) == 0) {
        if (chmod(tmp, 02775) != 0 && errno != EPERM && errno != EACCES) return false;
    } else if (errno != EEXIST) return false;
    return true;
}

static char *read_file(const char *path, size_t *len_out, char *erro, size_t errosz) {
    struct stat st;
    if (stat(path, &st) != 0) { snprintf(erro, errosz, "stat falhou: %s", strerror(errno)); return NULL; }
    if (!S_ISREG(st.st_mode)) { snprintf(erro, errosz, "nao e arquivo regular"); return NULL; }
    if (st.st_size < 0 || st.st_size > MAX_ARQ_BYTES) { snprintf(erro, errosz, "arquivo excede limite de bytes"); return NULL; }
    FILE *f = fopen(path, "rb");
    if (!f) { snprintf(erro, errosz, "fopen falhou: %s", strerror(errno)); return NULL; }
    char *buf = malloc((size_t)st.st_size + 1);
    if (!buf) { fclose(f); snprintf(erro, errosz, "memoria insuficiente"); return NULL; }
    size_t n = fread(buf, 1, (size_t)st.st_size, f);
    fclose(f);
    if (n != (size_t)st.st_size) { free(buf); snprintf(erro, errosz, "leitura incompleta"); return NULL; }
    buf[n] = 0;
    *len_out = n;
    erro[0] = 0;
    return buf;
}

static Json *json_new(JType t) {
    Json *j = calloc(1, sizeof(*j));
    if (!j) exit(70);
    j->type = t;
    return j;
}

static void json_free(Json *j) {
    if (!j) return;
    if (j->type == J_STRING) free(j->u.s);
    else if (j->type == J_ARRAY) {
        for (size_t i = 0; i < j->u.array.len; i++) json_free(j->u.array.items[i]);
        free(j->u.array.items);
    } else if (j->type == J_OBJECT) {
        for (size_t i = 0; i < j->u.object.len; i++) {
            free(j->u.object.pairs[i].key);
            json_free(j->u.object.pairs[i].value);
        }
        free(j->u.object.pairs);
    }
    free(j);
}

static void perr(Parser *p, const char *msg) { if (!p->erro[0]) snprintf(p->erro, sizeof p->erro, "%s", msg); }
static void pws(Parser *p) { while (p->p < p->end && isspace((unsigned char)*p->p)) p->p++; }
static bool plit(Parser *p, const char *lit) { size_t n = strlen(lit); if ((size_t)(p->end - p->p) >= n && strncmp(p->p, lit, n) == 0) { p->p += n; return true; } return false; }

static int hexv(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'a' && c <= 'f') return 10 + c - 'a';
    if (c >= 'A' && c <= 'F') return 10 + c - 'A';
    return -1;
}

static char *pstring(Parser *p) {
    if (p->p >= p->end || *p->p != '"') { perr(p, "string esperada"); return NULL; }
    p->p++;
    size_t cap = 64, len = 0;
    char *buf = calloc(cap, 1);
    if (!buf) exit(70);
    while (p->p < p->end) {
        unsigned char c = (unsigned char)*p->p++;
        if (c == '"') { buf[len] = 0; return buf; }
        if (c < 0x20) { free(buf); perr(p, "controle invalido em string"); return NULL; }
        if (c == '\\') {
            if (p->p >= p->end) { free(buf); perr(p, "escape incompleto"); return NULL; }
            char e = *p->p++;
            switch (e) {
                case '"': c = '"'; break;
                case '\\': c = '\\'; break;
                case '/': c = '/'; break;
                case 'b': c = '\b'; break;
                case 'f': c = '\f'; break;
                case 'n': c = '\n'; break;
                case 'r': c = '\r'; break;
                case 't': c = '\t'; break;
                case 'u':
                    if (p->end - p->p < 4 || hexv(p->p[0]) < 0 || hexv(p->p[1]) < 0 || hexv(p->p[2]) < 0 || hexv(p->p[3]) < 0) { free(buf); perr(p, "unicode escape invalido"); return NULL; }
                    p->p += 4; c = '?'; break;
                default: free(buf); perr(p, "escape invalido"); return NULL;
            }
        }
        if (len + 2 > cap) { cap *= 2; char *nb = realloc(buf, cap); if (!nb) exit(70); buf = nb; }
        buf[len++] = (char)c;
    }
    free(buf); perr(p, "string nao terminada"); return NULL;
}

static Json *pvalue(Parser *p);

static Json *parray(Parser *p) {
    p->p++;
    Json *a = json_new(J_ARRAY);
    pws(p);
    if (p->p < p->end && *p->p == ']') { p->p++; return a; }
    while (p->p < p->end) {
        Json *v = pvalue(p);
        if (!v) { json_free(a); return NULL; }
        Json **ni = realloc(a->u.array.items, sizeof(Json *) * (a->u.array.len + 1));
        if (!ni) exit(70);
        a->u.array.items = ni;
        a->u.array.items[a->u.array.len++] = v;
        pws(p);
        if (p->p < p->end && *p->p == ',') { p->p++; pws(p); continue; }
        if (p->p < p->end && *p->p == ']') { p->p++; return a; }
        json_free(a); perr(p, "',' ou ']' esperado"); return NULL;
    }
    json_free(a); perr(p, "array nao terminado"); return NULL;
}

static Json *pobject(Parser *p) {
    p->p++;
    Json *o = json_new(J_OBJECT);
    pws(p);
    if (p->p < p->end && *p->p == '}') { p->p++; return o; }
    while (p->p < p->end) {
        pws(p);
        char *k = pstring(p);
        if (!k) { json_free(o); return NULL; }
        pws(p);
        if (p->p >= p->end || *p->p != ':') { free(k); json_free(o); perr(p, "':' esperado"); return NULL; }
        p->p++;
        Json *v = pvalue(p);
        if (!v) { free(k); json_free(o); return NULL; }
        Pair *np = realloc(o->u.object.pairs, sizeof(Pair) * (o->u.object.len + 1));
        if (!np) exit(70);
        o->u.object.pairs = np;
        o->u.object.pairs[o->u.object.len].key = k;
        o->u.object.pairs[o->u.object.len].value = v;
        o->u.object.len++;
        pws(p);
        if (p->p < p->end && *p->p == ',') { p->p++; pws(p); continue; }
        if (p->p < p->end && *p->p == '}') { p->p++; return o; }
        json_free(o); perr(p, "',' ou '}' esperado"); return NULL;
    }
    json_free(o); perr(p, "objeto nao terminado"); return NULL;
}

static Json *pnumber(Parser *p) {
    const char *start = p->p;
    if (*p->p == '-') p->p++;
    if (p->p >= p->end) { perr(p, "numero invalido"); return NULL; }
    if (*p->p == '0') p->p++;
    else if (isdigit((unsigned char)*p->p)) while (p->p < p->end && isdigit((unsigned char)*p->p)) p->p++;
    else { perr(p, "numero invalido"); return NULL; }
    if (p->p < p->end && *p->p == '.') { p->p++; while (p->p < p->end && isdigit((unsigned char)*p->p)) p->p++; }
    if (p->p < p->end && (*p->p == 'e' || *p->p == 'E')) { p->p++; if (p->p < p->end && (*p->p == '+' || *p->p == '-')) p->p++; while (p->p < p->end && isdigit((unsigned char)*p->p)) p->p++; }
    char tmp[128]; size_t n = (size_t)(p->p - start); if (n >= sizeof tmp) { perr(p, "numero longo demais"); return NULL; }
    memcpy(tmp, start, n); tmp[n] = 0;
    Json *j = json_new(J_NUMBER); j->u.n = strtod(tmp, NULL); return j;
}

static Json *pvalue(Parser *p) {
    pws(p);
    if (p->p >= p->end) { perr(p, "valor esperado"); return NULL; }
    if (*p->p == '{') return pobject(p);
    if (*p->p == '[') return parray(p);
    if (*p->p == '"') { Json *j = json_new(J_STRING); j->u.s = pstring(p); if (!j->u.s) { free(j); return NULL; } return j; }
    if (*p->p == 't' && plit(p, "true")) { Json *j = json_new(J_BOOL); j->u.b = true; return j; }
    if (*p->p == 'f' && plit(p, "false")) { Json *j = json_new(J_BOOL); j->u.b = false; return j; }
    if (*p->p == 'n' && plit(p, "null")) return json_new(J_NULL);
    if (*p->p == '-' || isdigit((unsigned char)*p->p)) return pnumber(p);
    perr(p, "valor JSON invalido"); return NULL;
}

static Json *json_parse(const char *text, size_t len, char *erro, size_t errosz) {
    Parser p = { .p = text, .end = text + len, .erro = {0} };
    Json *j = pvalue(&p);
    if (!j) { snprintf(erro, errosz, "%s", p.erro[0] ? p.erro : "JSON invalido"); return NULL; }
    pws(&p);
    if (p.p != p.end) { json_free(j); snprintf(erro, errosz, "conteudo extra apos JSON"); return NULL; }
    erro[0] = 0;
    return j;
}

static Json *obj_get(Json *o, const char *key) {
    if (!o || o->type != J_OBJECT) return NULL;
    for (size_t i = 0; i < o->u.object.len; i++) if (strcmp(o->u.object.pairs[i].key, key) == 0) return o->u.object.pairs[i].value;
    return NULL;
}

static const char *obj_str(Json *o, const char *key) { Json *v = obj_get(o, key); return (v && v->type == J_STRING) ? v->u.s : NULL; }
static bool obj_bool(Json *o, const char *key, bool def) { Json *v = obj_get(o, key); return (v && v->type == J_BOOL) ? v->u.b : def; }
static bool obj_int(Json *o, const char *key, int *out) { Json *v = obj_get(o, key); if (!v || v->type != J_NUMBER) return false; *out = (int)v->u.n; return true; }

static bool manifesto_biblioteca_permitido(Json *root, const char *tipo) {
    if (strcmp(tipo, "agente") == 0) return obj_bool(root, "agenteFixo", false);
    if (strcmp(tipo, "conector") == 0) return obj_bool(root, "conectorFixo", true);
    return false;
}

static bool ends_with_str(const char *s, const char *suffix) {
    size_t a = strlen(s ? s : ""), b = strlen(suffix ? suffix : "");
    return a >= b && strcmp(s + a - b, suffix) == 0;
}

static bool rel_formato_conector(Json *manifesto, const char *id, char *out, size_t outsz, char *legado, size_t legadosz) {
    const char *decl = obj_str(manifesto, "formatoConector");
    if (legado && legadosz) legado[0] = 0;
    if (!decl || !*decl) {
        fmt_checked(out, outsz, "conectores/%s/formatos/formato-%s.json", id, id);
        if (legado && legadosz) fmt_checked(legado, legadosz, "conectores/formatos/formato-%s.json", id);
        return true;
    }
    while (!strncmp(decl, "./", 2)) decl += 2;
    if (!*decl || decl[0] == '/' || strstr(decl, "..") || !ends_with_str(decl, ".json")) return false;
    if (strchr(decl, '/')) {
        if (strncmp(decl, "conectores/", 11) != 0) return false;
        fmt_checked(out, outsz, "%s", decl);
        return true;
    }
    fmt_checked(out, outsz, "conectores/%s/formatos/%s", id, decl);
    if (legado && legadosz) fmt_checked(legado, legadosz, "conectores/formatos/%s", decl);
    return true;
}

static bool validar_formato_conector(const char *base, Json *manifesto, const char *id, bool biblioteca) {
    char rel[PATH_MAX], legado[PATH_MAX], path[PATH_MAX];
    if (!rel_formato_conector(manifesto, id, rel, sizeof rel, legado, sizeof legado)) return false;
    if (biblioteca) return true; /* pacotes da biblioteca são autocontidos; ao importar, o formato vai declarado no pacote. */
    path_join(path, sizeof path, base, rel);
    if (is_regular_file(path)) return true;
    if (legado[0]) {
        path_join(path, sizeof path, base, legado);
        if (is_regular_file(path)) return true;
    }
    return false;
}

static Json *load_json(const char *path, char *erro, size_t errosz) {
    size_t len = 0;
    char *txt = read_file(path, &len, erro, errosz);
    if (!txt) return NULL;
    Json *j = json_parse(txt, len, erro, errosz);
    free(txt);
    return j;
}

static bool valid_id(const char *s) {
    if (!s || !*s || strlen(s) > MAX_ID || !isalnum((unsigned char)s[0])) return false;
    for (size_t i = 1; s[i]; i++) if (!(isalnum((unsigned char)s[i]) || s[i] == '.' || s[i] == '_' || s[i] == '-')) return false;
    return true;
}

static bool starts_with(const char *s, const char *p) { return s && strncmp(s, p, strlen(p)) == 0; }

static bool valid_entidade(const char *s) {
    const char *id = NULL;
    if (starts_with(s, "agente__")) id = s + 8;
    else if (starts_with(s, "conector__")) id = s + 10;
    else if (starts_with(s, "sistema__")) id = s + 9;
    else return false;
    return valid_id(id);
}


static int extrair_protocolo(Json *msg, const char *tipo_runtime, Protocolo *p, char *erro, size_t errosz) {
    if (!msg || msg->type != J_OBJECT) { snprintf(erro, errosz, "mensagem deve ser objeto JSON"); return 65; }
    Json *proto = obj_get(msg, "_protocolo");
    if (!proto || proto->type != J_OBJECT) { snprintf(erro, errosz, "_protocolo ausente ou invalido"); return 65; }
    const char *nome = obj_str(proto, "nome");
    int versao = 0;
    if (!nome || strcmp(nome, PROTO_NOME) != 0) { snprintf(erro, errosz, "_protocolo.nome invalido"); return 65; }
    if (!obj_int(proto, "versao", &versao) || versao != PROTO_VERSAO) { snprintf(erro, errosz, "_protocolo.versao invalida"); return 65; }
    const char *pid = obj_str(proto, "processoId");
    const char *mid = obj_str(proto, "mensagemId");
    const char *orig = obj_str(proto, "origem");
    const char *dest = obj_str(proto, "destino");
    const char *tipo_msg = obj_str(proto, "tipo");
    if (!valid_id(pid)) { snprintf(erro, errosz, "processoId invalido"); return 65; }
    if (!valid_id(mid)) { snprintf(erro, errosz, "mensagemId invalido"); return 65; }
    if (!valid_entidade(orig)) { snprintf(erro, errosz, "origem invalida"); return 65; }
    if (!valid_entidade(dest)) { snprintf(erro, errosz, "destino invalido"); return 65; }
    char pref[32]; fmt_checked(pref, sizeof pref, "%s__", tipo_runtime);
    if (!starts_with(dest, pref)) { snprintf(erro, errosz, "mensagem destinada a %s, mas runtime e %s", dest, tipo_runtime); return 65; }
    snprintf(p->processo_id, sizeof p->processo_id, "%s", pid);
    snprintf(p->mensagem_id, sizeof p->mensagem_id, "%s", mid);
    snprintf(p->origem, sizeof p->origem, "%s", orig);
    snprintf(p->destino, sizeof p->destino, "%s", dest);
    snprintf(p->ator_id, sizeof p->ator_id, "%s", dest + strlen(pref));
    snprintf(p->tipo_msg, sizeof p->tipo_msg, "%s", tipo_msg ? tipo_msg : "");
    const char *resp = obj_str(proto, "respostaA");
    snprintf(p->resposta_a, sizeof p->resposta_a, "%s", (resp && resp[0]) ? resp : "");
    return 0;
}

static const char *first_str(Json *o, const char **keys, size_t nkeys) {
    if (!o || o->type != J_OBJECT) return NULL;
    for (size_t i = 0; i < nkeys; i++) {
        const char *s = obj_str(o, keys[i]);
        if (s && *s) return s;
    }
    return NULL;
}

static const char *handler_ler_mensagem(Json *manifesto) {
    const char *top[] = {"lerMensagem", "ler-mensagem", "handlerLerMensagem"};
    const char *s = first_str(manifesto, top, sizeof(top)/sizeof(top[0]));
    if (s) return s;
    Json *controlador = obj_get(manifesto, "controlador");
    const char *ctrl[] = {"lerMensagem", "ler-mensagem", "handlerLerMensagem", "comandoLerMensagem"};
    s = first_str(controlador, ctrl, sizeof(ctrl)/sizeof(ctrl[0]));
    if (s) return s;
    Json *metodos = obj_get(manifesto, "metodos");
    const char *met[] = {"lerMensagem", "ler-mensagem"};
    return first_str(metodos, met, sizeof(met)/sizeof(met[0]));
}

static void shell_quote(const char *in, char *out, size_t outsz) {
    size_t j = 0;
    if (j + 1 >= outsz) exit(70);
    out[j++] = '\'';
    for (size_t i = 0; in && in[i]; i++) {
        if (in[i] == '\'') {
            const char *q = "'\\''";
            size_t qn = strlen(q);
            if (j + qn >= outsz) exit(70);
            memcpy(out + j, q, qn); j += qn;
        } else {
            if (j + 1 >= outsz) exit(70);
            out[j++] = in[i];
        }
    }
    if (j + 1 >= outsz) exit(70);
    out[j++] = '\''; out[j] = 0;
}

static bool rel_path_seguro(const char *rel) {
    if (!rel || !*rel || rel[0] == '/') return false;
    const char *p = rel;
    while (*p) {
        const char *slash = strchr(p, '/');
        size_t n = slash ? (size_t)(slash - p) : strlen(p);
        if (n == 0) return false;
        if ((n == 1 && p[0] == '.') || (n == 2 && p[0] == '.' && p[1] == '.')) return false;
        for (size_t i = 0; i < n; i++) if ((unsigned char)p[i] < 32) return false;
        if (!slash) break;
        p = slash + 1;
    }
    return true;
}

static int preparar_handler_conector(const char *base, const char *ator_id,
                                     const char *handler, char *out, size_t outsz) {
    const char *rel = handler ? handler : "";
    while (strncmp(rel, "./", 2) == 0) rel += 2;

    char pref[PATH_MAX];
    fmt_checked(pref, sizeof pref, "conectores/%s/handlers/", ator_id);
    if (!rel_path_seguro(rel) || !starts_with(rel, pref)) {
        die_msg("ERRO: handler de conector deve ser caminho executavel direto em %s", pref);
        return 78;
    }

    fmt_checked(out, outsz, "%s/%s", base, rel);
    if (!is_regular_file(out) || access(out, X_OK) != 0) {
        die_msg("ERRO: handler de conector ausente ou sem permissao de execucao: %s", rel);
        return 78;
    }
    return 0;
}

static void sanitize(const char *in, char *out, size_t outsz);

static void publicar_saida_handler(const char *base, const char *mensagem_id, const char *tmp_out) {
    char erro[256] = "";
    size_t len = 0;
    char *txt = read_file(tmp_out, &len, erro, sizeof erro);
    if (!txt) return;
    if (len > 0) {
        fwrite(txt, 1, len, stdout);
        fflush(stdout);
        if (mensagem_id && mensagem_id[0]) {
            char dir[PATH_MAX], mid[256], out[PATH_MAX], tmp[PATH_MAX];
            path_join(dir, sizeof dir, base, "espaco/tmp/respostas");
            if (mkdir_p(dir)) {
                sanitize(mensagem_id, mid, sizeof mid);
                fmt_checked(out, sizeof out, "%s/%s.out", dir, mid);
                fmt_checked(tmp, sizeof tmp, "%s.tmp.%ld", out, (long)getpid());
                FILE *f = fopen(tmp, "wb");
                if (f) {
                    fwrite(txt, 1, len, f);
                    fflush(f);
                    fsync(fileno(f));
                    if (fclose(f) == 0) {
                        chmod(tmp, 0664);
                        rename(tmp, out);
                    } else {
                        unlink(tmp);
                    }
                }
            }
        }
    }
    free(txt);
}

static int executar_handler(const char *tipo, const char *base, const char *ator_id,
                            const char *handler, const char *arquivo_msg, const char *mensagem_id) {
    char q[PATH_MAX * 2], cmd[8192];
    shell_quote(arquivo_msg, q, sizeof q);

    if (strcmp(tipo, "conector") == 0) {
        char handler_exec[PATH_MAX], hq[PATH_MAX * 2];
        int prep = preparar_handler_conector(base, ator_id, handler, handler_exec, sizeof handler_exec);
        if (prep != 0) return prep;
        shell_quote(handler_exec, hq, sizeof hq);

        if (!getenv("SISC_SANDBOX_DESATIVADO")) {
            /* Handlers de conector sao codigo de terceiros: executa isolado em
             * namespaces para esconder espaco/ e secretos/ (exceto o segredo
             * do proprio conector). Falha fechada se o sandbox nao existir. */
            char sb[PATH_MAX], bq[PATH_MAX * 2], iq[PATH_MAX * 2], sq[PATH_MAX * 2];
            fmt_checked(sb, sizeof sb, "%s/escuta/sandbox-handler", base);
            if (!is_regular_file(sb) || access(sb, X_OK) != 0) {
                die_msg("ERRO: sandbox-handler ausente/sem execucao em escuta/; handler nao sera executado sem isolamento");
                return 78;
            }
            shell_quote(sb, sq, sizeof sq);
            shell_quote(base, bq, sizeof bq);
            shell_quote(ator_id, iq, sizeof iq);
            fmt_checked(cmd, sizeof cmd, "%s %s %s %s %s", sq, bq, iq, q, hq);
        } else {
            fmt_checked(cmd, sizeof cmd, "%s %s", hq, q);
        }
    } else {
        fmt_checked(cmd, sizeof cmd, "%s %s", handler, q);
    }

    char tmpdir[PATH_MAX], tmpout[PATH_MAX], tmpq[PATH_MAX * 2], cmd_exec[10000];
    path_join(tmpdir, sizeof tmpdir, base, "espaco/tmp");
    mkdir_p(tmpdir);
    fmt_checked(tmpout, sizeof tmpout, "%s/runtime-saida-%ld-%d.out", tmpdir, (long)time(NULL), getpid());
    shell_quote(tmpout, tmpq, sizeof tmpq);
    fmt_checked(cmd_exec, sizeof cmd_exec, "%s > %s 2>&1", cmd, tmpq);

    int rc = system(cmd_exec);
    publicar_saida_handler(base, mensagem_id, tmpout);
    unlink(tmpout);
    if (rc == -1) return 70;
    if (WIFEXITED(rc)) return WEXITSTATUS(rc);
    if (WIFSIGNALED(rc)) return 128 + WTERMSIG(rc);
    return 70;
}

static void json_escape(FILE *f, const char *s) {
    fputc('"', f);
    for (const unsigned char *p = (const unsigned char *)(s ? s : ""); *p; p++) {
        switch (*p) {
            case '"': fputs("\\\"", f); break;
            case '\\': fputs("\\\\", f); break;
            case '\n': fputs("\\n", f); break;
            case '\r': fputs("\\r", f); break;
            case '\t': fputs("\\t", f); break;
            default: if (*p < 0x20) fprintf(f, "\\u%04x", *p); else fputc(*p, f);
        }
    }
    fputc('"', f);
}

static void sanitize(const char *in, char *out, size_t outsz) {
    size_t j = 0;
    for (size_t i = 0; in && in[i] && j + 1 < outsz; i++) {
        unsigned char c = (unsigned char)in[i];
        out[j++] = (isalnum(c) || c == '.' || c == '_' || c == '-') ? (char)c : '-';
    }
    if (j == 0 && outsz > 1) out[j++] = 'x';
    out[j] = 0;
}

static int registrar_sem_handler(const char *base, const char *tipo, const Protocolo *p, const char *arquivo_msg) {
    char rt[PATH_MAX], dir[PATH_MAX], aid[256], mid[256], out[PATH_MAX], tmp[PATH_MAX];
    path_join(rt, sizeof rt, base, "espaco/runtime");
    path_join(dir, sizeof dir, rt, tipo);
    if (!mkdir_p(dir)) return 74;
    sanitize(p->ator_id, aid, sizeof aid);
    sanitize(p->mensagem_id, mid, sizeof mid);
    fmt_checked(out, sizeof out, "%s/%s__%s.json", dir, aid, mid);
    fmt_checked(tmp, sizeof tmp, "%s.tmp.%ld", out, (long)getpid());
    FILE *f = fopen(tmp, "w");
    if (!f) return 74;
    time_t now = time(NULL);
    struct tm tm; gmtime_r(&now, &tm);
    char iso[64]; strftime(iso, sizeof iso, "%Y-%m-%dT%H:%M:%SZ", &tm);
    fputs("{\n  \"status\": \"recebida_sem_handler\",\n", f);
    fputs("  \"observacao\": \"Runtime C validou e leu a mensagem, mas o manifesto do ator nao declarou handler especifico de ler-mensagem.\",\n", f);
    fputs("  \"tipoAtor\": ", f); json_escape(f, tipo); fputs(",\n", f);
    fputs("  \"atorId\": ", f); json_escape(f, p->ator_id); fputs(",\n", f);
    fputs("  \"arquivoMensagem\": ", f); json_escape(f, arquivo_msg); fputs(",\n", f);
    fputs("  \"registradoEm\": ", f); json_escape(f, iso); fputs(",\n", f);
    fputs("  \"protocolo\": {\n", f);
    fputs("    \"processoId\": ", f); json_escape(f, p->processo_id); fputs(",\n", f);
    fputs("    \"mensagemId\": ", f); json_escape(f, p->mensagem_id); fputs(",\n", f);
    fputs("    \"origem\": ", f); json_escape(f, p->origem); fputs(",\n", f);
    fputs("    \"destino\": ", f); json_escape(f, p->destino); fputs(",\n", f);
    fputs("    \"respostaA\": ", f); json_escape(f, p->resposta_a[0] ? p->resposta_a : ""); fputs("\n  }\n}\n", f);
    fflush(f);
    fsync(fileno(f));
    fclose(f);
    if (rename(tmp, out) != 0) { unlink(tmp); return 74; }
    return 0;
}

static int validar_args(const char *tipo, int argc, char **argv, const char **arquivo, bool *lote) {
    *lote = false;
    if (strcmp(tipo, "agente") == 0) {
        if (argc == 2 && strcmp(argv[0], "--ler-mensagem") == 0) { *arquivo = argv[1]; return 0; }
        if (argc == 2 && strcmp(argv[0], "--ler-mensagens-arquivo") == 0) { *arquivo = argv[1]; *lote = true; return 0; }
        die_msg("ERRO: uso: runtime-agente --ler-mensagem <arquivo-mensagem> | --ler-mensagens-arquivo <arquivo-lista>"); return 64;
    }
    if (strcmp(tipo, "conector") == 0) {
        if (argc == 2 && strcmp(argv[0], "ler-mensagem") == 0) { *arquivo = argv[1]; return 0; }
        if (argc == 2 && strcmp(argv[0], "ler-mensagens-arquivo") == 0) { *arquivo = argv[1]; *lote = true; return 0; }
        die_msg("ERRO: uso: runtime-conector ler-mensagem <arquivo-mensagem> | ler-mensagens-arquivo <arquivo-lista>"); return 64;
    }
    die_msg("ERRO: tipo de runtime invalido"); return 64;
}

int siscore_runtime_main(const char *tipo, int argc, char **argv) {
    const char *arquivo_arg = NULL;
    bool modo_lote = false;
    int rc = validar_args(tipo, argc, argv, &arquivo_arg, &modo_lote);
    if (rc != 0) return rc;

    char base[PATH_MAX];
    if (!getcwd(base, sizeof base)) { die_msg("ERRO: getcwd falhou: %s", strerror(errno)); return 70; }

    char arquivo[PATH_MAX];
    if (arquivo_arg[0] == '/') fmt_checked(arquivo, sizeof arquivo, "%s", arquivo_arg);
    else fmt_checked(arquivo, sizeof arquivo, "%s/%s", base, arquivo_arg);

    if (modo_lote) {
        FILE *lf = fopen(arquivo, "r");
        if (!lf) { die_msg("ERRO: nao foi possivel abrir arquivo-lista: %s", strerror(errno)); return 66; }
        char linha[PATH_MAX];
        int rc_final = 0;
        const char *flag = strcmp(tipo, "agente") == 0 ? "--ler-mensagem" : "ler-mensagem";
        while (fgets(linha, sizeof linha, lf)) {
            linha[strcspn(linha, "\r\n")] = 0;
            if (!linha[0]) continue;
            char *um[2];
            um[0] = (char *)flag;
            um[1] = linha;
            int r = siscore_runtime_main(tipo, 2, um);
            if (r != 0 && rc_final == 0) rc_final = r;
        }
        fclose(lf);
        return rc_final;
    }

    char erro[256];
    Json *msg = load_json(arquivo, erro, sizeof erro);
    if (!msg) { die_msg("ERRO: %s", erro); return 65; }

    Protocolo proto;
    memset(&proto, 0, sizeof proto);
    rc = extrair_protocolo(msg, tipo, &proto, erro, sizeof erro);
    json_free(msg);
    if (rc != 0) { die_msg("ERRO: %s", erro); return rc; }

    char pasta[32];
    snprintf(pasta, sizeof pasta, "%s", strcmp(tipo, "agente") == 0 ? "agentes" : "conectores");
    char dir[PATH_MAX], man[PATH_MAX], man_nome[256];
    bool fallback_biblioteca = false;
    path_join(dir, sizeof dir, base, pasta);
    Json *manifesto = NULL;
    fmt_checked(man_nome, sizeof man_nome, "%s.json", proto.ator_id);
    if (strcmp(tipo, "conector") == 0) {
        char sub[PATH_MAX];
        fmt_checked(sub, sizeof sub, "%s/%s.json", proto.ator_id, proto.ator_id);
        path_join(man, sizeof man, dir, sub);
    } else {
        path_join(man, sizeof man, dir, man_nome);
    }
    if (is_regular_file(man)) {
        manifesto = load_json(man, erro, sizeof erro);
    } else {
        if (strcmp(tipo, "conector") == 0) {
            path_join(man, sizeof man, dir, man_nome); /* compatibilidade com layout legado */
            if (is_regular_file(man)) manifesto = load_json(man, erro, sizeof erro);
        }
        if (!manifesto) {
            char raiz_sisc[PATH_MAX], subdir[64];
            if (raiz_sisc_de_base(base, raiz_sisc, sizeof raiz_sisc) == 0) {
                snprintf(subdir, sizeof subdir, "%s", strcmp(tipo, "agente") == 0 ? "core/biblioteca-agentes-fixos" : "core/biblioteca-conectores");
                path_join(dir, sizeof dir, raiz_sisc, subdir);
                path_join(man, sizeof man, dir, man_nome);
                if (is_regular_file(man)) {
                    manifesto = load_json(man, erro, sizeof erro);
                    if (manifesto) fallback_biblioteca = true;
                }
            }
        }
    }
    if (!manifesto) { die_msg("ERRO: manifesto do %s nao encontrado/invalido: %s", tipo, erro); return 66; }
    if (fallback_biblioteca && !manifesto_biblioteca_permitido(manifesto, tipo)) {
        json_free(manifesto);
        die_msg("ERRO: manifesto do %s em biblioteca nao e fixo/permitido para fallback: %s", tipo, proto.ator_id);
        return 66;
    }
    if (strcmp(tipo, "conector") == 0 && !validar_formato_conector(base, manifesto, proto.ator_id, fallback_biblioteca)) {
        json_free(manifesto);
        die_msg("ERRO: formato do conector nao encontrado na origem correspondente: %s", proto.ator_id);
        return 66;
    }
    bool ativo = obj_bool(manifesto, "ativo", true);
    if (!ativo) { json_free(manifesto); die_msg("ERRO: %s %s esta inativo", tipo, proto.ator_id); return 69; }
    const char *handler = handler_ler_mensagem(manifesto);
    bool exigir = obj_bool(manifesto, "exigirHandlerLerMensagem", false);
    if (handler && *handler) {
        char handler_copy[4096];
        fmt_checked(handler_copy, sizeof handler_copy, "%s", handler);
        json_free(manifesto);
        return executar_handler(tipo, base, proto.ator_id, handler_copy, arquivo, proto.mensagem_id);
    }
    json_free(manifesto);
    if (exigir) { die_msg("ERRO: %s %s exige handler ler-mensagem, mas nenhum foi declarado", tipo, proto.ator_id); return 78; }
    return registrar_sem_handler(base, tipo, &proto, arquivo);
}
