/*
 * empacotar-conector - cria pacote autocontido de um conector na biblioteca central
 * Uso: pp --empacotar-conector=<nome>
 *
 * O pacote gerado fica em sisc/core/biblioteca-conectores/<nome>.tar.gz e contem
 * os arquivos locais que compoem o conector: manifesto, handler declarado, formato
 * e, quando existir, o bloco do catalogo web-api referente ao destino conector__<nome>.
 * Segredos nao sao empacotados.
 */

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

static void uso(void) {
    fputs(
        "Uso:\n"
        "  pp --empacotar-conector=<nome>\n"
        "  pp --empacotar-conector <nome>\n"
        "\n"
        "Descrição:\n"
        "  Cria um pacote autocontido do conector atual em:\n"
        "    sisc/core/biblioteca-conectores/<nome>.tar.gz\n"
        "  Inclui conectores/<nome>/<nome>.json, o handler declarado em handlerLerMensagem,\n"
        "  conectores/<nome>/formatos/<formato>.json, arquivos extras declarados em\n"
        "  arquivosPacote ou em dependencias.arquivos do manifesto e, quando existir,\n"
        "  o bloco do catalogo web-api para destino conector__<nome>. Atualiza\n"
        "  dependencias no manifesto do pacote. Não inclui secretos/.\n"
        "\n"
        "Flexibilidade:\n"
        "  Para conectores com estruturas proprias, declare no manifesto em\n"
        "  dependencias.arquivos ou em arquivosPacote:\n"
        "    [\"conectores/<nome>/\", \"conectores/<nome>/handlers/lib.php\"]\n"
        "  Cada item deve ser caminho relativo seguro sob conectores/. Diretorios\n"
        "  sao copiados recursivamente; secretos/ e caminhos absolutos sao proibidos.\n"
        "\n"
        "Exemplo:\n"
        "  pp --empacotar-conector=conector-email\n",
        stdout);
}

static int nome_seguro(const char *s) {
    if (!s || !*s || strlen(s) > 128) return 0;
    for (const unsigned char *p=(const unsigned char*)s; *p; p++)
        if (!isalnum(*p) && *p!='-' && *p!='_') return 0;
    return strstr(s, "..") == NULL;
}

static int termina_com(const char *s, const char *suf) {
    size_t a=strlen(s), b=strlen(suf);
    return a>=b && strcmp(s+a-b,suf)==0;
}

static int garantir_dir(const char *dir, mode_t modo) {
    struct stat st;
    if (stat(dir,&st)==0) return S_ISDIR(st.st_mode)?0:-1;
    if (mkdir(dir,modo)!=0 && errno!=EEXIST) return -1;
    chmod(dir,modo);
    return 0;
}

static int mkdir_p(const char *path, mode_t modo) {
    char tmp[PATH_MAX*2];
    snprintf(tmp,sizeof(tmp),"%s",path?path:"");
    for (char *p=tmp+1; *p; p++) {
        if (*p=='/') { *p='\0'; if (garantir_dir(tmp,modo)!=0) return -1; *p='/'; }
    }
    return garantir_dir(tmp,modo);
}

static int garantir_dir_pai(const char *path, mode_t modo) {
    char dir[PATH_MAX*2];
    snprintf(dir,sizeof(dir),"%s",path?path:"");
    char *s=strrchr(dir,'/');
    if (!s) return 0;
    if (s==dir) s[1]='\0'; else *s='\0';
    return mkdir_p(dir,modo);
}

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

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;
}

static int copiar_arquivo(const char *origem, const char *destino, mode_t modo) {
    FILE *in=fopen(origem,"rb");
    if(!in) return -1;
    if(garantir_dir_pai(destino,0775)!=0){fclose(in);return -1;}
    FILE *out=fopen(destino,"wb");
    if(!out){fclose(in);return -1;}
    char buf[8192]; size_t n; int ok=0;
    while((n=fread(buf,1,sizeof(buf),in))>0) if(fwrite(buf,1,n,out)!=n){ok=-1;break;}
    if(ferror(in)) ok=-1;
    fclose(in); fclose(out); chmod(destino,modo);
    return ok;
}

typedef struct {
    char **v;
    int n;
    int cap;
} RelList;

static void rellist_init(RelList *l) { memset(l,0,sizeof(*l)); }

static int rellist_contem(RelList *l, const char *rel) {
    for (int i=0; i<l->n; i++) if (!strcmp(l->v[i],rel)) return 1;
    return 0;
}

static int rellist_add(RelList *l, const char *rel) {
    if (!rel || !*rel) return -1;
    if (rellist_contem(l,rel)) return 0;
    if (l->n >= l->cap) {
        int ncap = l->cap ? l->cap * 2 : 16;
        char **nv = realloc(l->v, (size_t)ncap * sizeof(char*));
        if (!nv) return -1;
        l->v = nv; l->cap = ncap;
    }
    l->v[l->n] = strdup(rel);
    if (!l->v[l->n]) return -1;
    l->n++;
    return 0;
}

static void rellist_free(RelList *l) {
    if (!l) return;
    for (int i=0; i<l->n; i++) free(l->v[i]);
    free(l->v);
    memset(l,0,sizeof(*l));
}

static const char *basename_seguro(const char *p) {
    const char *s=p?strrchr(p,'/'):NULL;
    return s?s+1:(p?p:"");
}

static int extrair_string_json(const char *json, const char *chave, char *out, size_t outsz) {
    char padrao[128]; snprintf(padrao,sizeof(padrao),"\"%s\"",chave);
    const char *p=strstr(json,padrao); if(!p) return 0;
    p += strlen(padrao);
    while(*p && isspace((unsigned char)*p)) p++;
    if(*p!=':') return -1;
    p++;
    while(*p && isspace((unsigned char)*p)) p++;
    if(*p!='\"') return -1;
    p++;
    size_t n=0;
    while(*p && *p!='\"') {
        char c=*p++;
        if(c=='\\' && *p) c=*p++;
        if(n+1<outsz) out[n++]=c;
    }
    if(*p!='\"') return -1;
    out[n]='\0'; return 1;
}

static void json_escape_out(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 '\b': fputs("\\b", f); break;
            case '\f': fputs("\\f", f); break;
            case '\n': fputs("\\n", f); break;
            case '\r': fputs("\\r", f); break;
            case '\t': fputs("\\t", f); break;
            default:
                if (*p < 32) fprintf(f, "\\u%04x", *p);
                else fputc(*p, f);
        }
    }
    fputc('"', f);
}

static const char *json_composto_fim(const char *ini, char abre, char fecha, const char *limite) {
    int nivel=0, em_string=0, escape=0;
    for (const char *p=ini; p && *p && (!limite || p<=limite); p++) {
        if (em_string) {
            if (escape) escape=0;
            else if (*p=='\\') escape=1;
            else if (*p=='"') em_string=0;
            continue;
        }
        if (*p=='"') em_string=1;
        else if (*p==abre) nivel++;
        else if (*p==fecha) {
            nivel--;
            if (nivel==0) return p;
            if (nivel<0) return NULL;
        }
    }
    return NULL;
}

static int localizar_array_json_chave(const char *json, const char *chave, const char **ini, const char **fim) {
    char padrao[128]; snprintf(padrao,sizeof(padrao),"\"%s\"",chave);
    const char *p=json;
    while ((p=strstr(p,padrao)) != NULL) {
        p += strlen(padrao);
        while (*p && isspace((unsigned char)*p)) p++;
        if (*p!=':') continue;
        p++;
        while (*p && isspace((unsigned char)*p)) p++;
        if (*p!='[') continue;
        const char *f=json_composto_fim(p,'[',']',NULL);
        if (!f) return -1;
        *ini=p; *fim=f; return 0;
    }
    return -1;
}

static int localizar_objeto_json_chave(const char *json, const char *chave, const char **ini, const char **fim) {
    char padrao[128]; snprintf(padrao,sizeof(padrao),"\"%s\"",chave);
    const char *p=json;
    while ((p=strstr(p,padrao)) != NULL) {
        p += strlen(padrao);
        while (*p && isspace((unsigned char)*p)) p++;
        if (*p!=':') continue;
        p++;
        while (*p && isspace((unsigned char)*p)) p++;
        if (*p!='{') continue;
        const char *f=json_composto_fim(p,'{','}',NULL);
        if (!f) return -1;
        *ini=p; *fim=f; return 0;
    }
    return -1;
}

static int proximo_objeto_json(const char *p, const char *limite, const char **ini, const char **fim) {
    int em_string=0, escape=0;
    for (; p && p<limite && *p; p++) {
        if (em_string) {
            if (escape) escape=0;
            else if (*p=='\\') escape=1;
            else if (*p=='"') em_string=0;
            continue;
        }
        if (*p=='"') em_string=1;
        else if (*p=='{') {
            const char *f=json_composto_fim(p,'{','}',limite);
            if (!f) return -1;
            *ini=p; *fim=f; return 1;
        }
    }
    return 0;
}

static int stringlist_add_unico(char ***v, int *n, int *cap, const char *s) {
    if (!s || !*s) return 0;
    for (int i=0;i<*n;i++) if (!strcmp((*v)[i],s)) return 0;
    if (*n >= *cap) {
        int ncap = *cap ? *cap * 2 : 8;
        char **nv = realloc(*v, (size_t)ncap * sizeof(char*));
        if (!nv) return -1;
        *v = nv; *cap = ncap;
    }
    (*v)[*n] = strdup(s);
    if (!(*v)[*n]) return -1;
    (*n)++;
    return 0;
}

static void stringlist_free(char **v, int n) {
    for (int i=0;i<n;i++) free(v[i]);
    free(v);
}

static int coletar_strings_array_json(const char *json, const char *chave, char ***out, int *n_out, int *cap_out) {
    const char *arr_ini=NULL, *arr_fim=NULL;
    if (localizar_array_json_chave(json,chave,&arr_ini,&arr_fim)!=0) return 0;
    const char *p=arr_ini+1;
    while (p<arr_fim) {
        while (p<arr_fim && (isspace((unsigned char)*p) || *p==',')) p++;
        if (p>=arr_fim) break;
        if (*p=='"') {
            p++;
            char buf[PATH_MAX*2]; size_t j=0; int escape=0;
            while (p<arr_fim && *p) {
                if (escape) {
                    if (j+1<sizeof(buf)) buf[j++]=*p;
                    escape=0;
                } else if (*p=='\\') {
                    escape=1;
                } else if (*p=='"') {
                    p++;
                    break;
                } else {
                    if (j+1<sizeof(buf)) buf[j++]=*p;
                }
                p++;
            }
            buf[j]='\0';
            if (stringlist_add_unico(out,n_out,cap_out,buf)!=0) return -1;
        } else if (*p=='{' || *p=='[') {
            const char *f=json_composto_fim(p,*p,*p=='{'?'}':']',arr_fim);
            if (!f) return -1;
            p=f+1;
        } else {
            while (p<arr_fim && *p!=',') p++;
        }
    }
    return 0;
}

static char *substr_objeto_json(const char *ini, const char *fim);

static int rel_declarado_empacotavel(const char *rel) {
    if(!rel || !*rel) return 0;
    while(!strncmp(rel,"./",2)) rel += 2;
    if(!*rel || rel[0]=='/' || strstr(rel,"..")) return -1;
    if(!strcmp(rel,"CATALOGO-MENSAGENS-CONECTOR.json") || !strncmp(rel,"web-api/",8)) return 0;
    if(strncmp(rel,"conectores/",11)!=0) return -1;
    if(!strcmp(rel,"conectores") || !strcmp(rel,"conectores/")) return -1;
    if(strstr(rel,"/secretos/") || strstr(rel,"/secrets/") || termina_com(rel,"/secretos") || termina_com(rel,"/secrets")) return -1;
    return 1;
}

static int adicionar_rel_declarado(char ***out, int *n_out, int *cap, const char *rel) {
    if(!rel || !*rel) return 0;
    while(!strncmp(rel,"./",2)) rel += 2;
    int seguro=rel_declarado_empacotavel(rel);
    if(seguro < 0) return -1;
    if(seguro == 0) return 0;
    return stringlist_add_unico(out,n_out,cap,rel);
}

static int coletar_dependencias_arquivos_manifesto(const char *manifesto, char ***out, int *n_out, int *cap) {
    const char *dep_ini=NULL,*dep_fim=NULL;
    if(localizar_objeto_json_chave(manifesto,"dependencias",&dep_ini,&dep_fim)!=0) return 0;
    char *dep=substr_objeto_json(dep_ini,dep_fim);
    if(!dep) return -1;
    const char *arr_ini=NULL,*arr_fim=NULL;
    if(localizar_array_json_chave(dep,"arquivos",&arr_ini,&arr_fim)!=0){ free(dep); return 0; }
    const char *p=arr_ini+1;
    while(p<arr_fim) {
        while(p<arr_fim && (isspace((unsigned char)*p) || *p==',')) p++;
        if(p>=arr_fim) break;
        if(*p=='\"') {
            p++;
            char rel[PATH_MAX*2]; size_t j=0; int escape=0;
            while(p<arr_fim && *p) {
                if(escape){ if(j+1<sizeof(rel)) rel[j++]=*p; escape=0; }
                else if(*p=='\\') escape=1;
                else if(*p=='\"'){ p++; break; }
                else { if(j+1<sizeof(rel)) rel[j++]=*p; }
                p++;
            }
            rel[j]='\0';
            if(adicionar_rel_declarado(out,n_out,cap,rel)!=0){ free(dep); return -1; }
        } else if(*p=='{') {
            const char *of=json_composto_fim(p,'{','}',arr_fim);
            if(!of){ free(dep); return -1; }
            char *obj=substr_objeto_json(p,of);
            if(!obj){ free(dep); return -1; }
            char rel[PATH_MAX*2]="";
            if(!extrair_string_json(obj,"origem",rel,sizeof(rel)))
                if(!extrair_string_json(obj,"arquivo",rel,sizeof(rel)))
                    extrair_string_json(obj,"caminho",rel,sizeof(rel));
            if(!rel[0]) extrair_string_json(obj,"destino",rel,sizeof(rel));
            free(obj);
            if(rel[0] && adicionar_rel_declarado(out,n_out,cap,rel)!=0){ free(dep); return -1; }
            p=of+1;
        } else if(*p=='[') {
            const char *f=json_composto_fim(p,'[',']',arr_fim);
            if(!f){ free(dep); return -1; }
            p=f+1;
        } else {
            while(p<arr_fim && *p!=',') p++;
        }
    }
    free(dep);
    return 0;
}

static int coletar_arquivos_pacote_manifesto(const char *manifesto, char ***out, int *n_out) {
    *out=NULL; *n_out=0;
    int cap=0;
    const char *chaves[] = {"arquivosPacote", "arquivosExtras", "dependenciasArquivos", NULL};
    for (int i=0; chaves[i]; i++)
        if (coletar_strings_array_json(manifesto,chaves[i],out,n_out,&cap)!=0) { stringlist_free(*out,*n_out); *out=NULL; *n_out=0; return -1; }
    if(coletar_dependencias_arquivos_manifesto(manifesto,out,n_out,&cap)!=0) { stringlist_free(*out,*n_out); *out=NULL; *n_out=0; return -1; }
    return 0;
}

static char *substr_objeto_json(const char *ini, const char *fim) {
    if (!ini || !fim || fim<ini) return NULL;
    size_t n=(size_t)(fim-ini+1);
    char *s=malloc(n+1);
    if (!s) return NULL;
    memcpy(s,ini,n); s[n]='\0';
    return s;
}

static void liberar_blocos(char **blocos, int n) {
    if (!blocos) return;
    for (int i=0;i<n;i++) free(blocos[i]);
    free(blocos);
}

static int coletar_blocos_catalogo_destino(const char *catalogo, const char *destino, char ***blocos_out, int *n_out) {
    *blocos_out=NULL; *n_out=0;
    const char *arr_ini=NULL, *arr_fim=NULL;
    if (localizar_array_json_chave(catalogo,"mensagens",&arr_ini,&arr_fim)!=0) return 0;
    int cap=0,n=0;
    const char *p=arr_ini+1;
    while (p<arr_fim) {
        const char *oi=NULL,*of=NULL;
        int r=proximo_objeto_json(p,arr_fim,&oi,&of);
        if (r<0) { liberar_blocos(*blocos_out,n); *blocos_out=NULL; *n_out=0; return -1; }
        if (r==0) break;
        char *obj=substr_objeto_json(oi,of);
        if (!obj) { liberar_blocos(*blocos_out,n); *blocos_out=NULL; *n_out=0; return -1; }
        char dest[PATH_MAX]="";
        if (extrair_string_json(obj,"destino",dest,sizeof(dest))>0 && strcmp(dest,destino)==0) {
            if (n>=cap) {
                cap=cap?cap*2:4;
                char **nv=realloc(*blocos_out,(size_t)cap*sizeof(char*));
                if (!nv) { free(obj); liberar_blocos(*blocos_out,n); *blocos_out=NULL; *n_out=0; return -1; }
                *blocos_out=nv;
            }
            (*blocos_out)[n++]=obj;
        } else {
            free(obj);
        }
        p=of+1;
    }
    *n_out=n;
    return 0;
}

static void escrever_bloco_indentado(FILE *f, const char *txt, const char *indent) {
    fputs(indent, f);
    for (const char *p=txt?txt:""; *p; p++) {
        fputc(*p, f);
        if (*p=='\n' && p[1]) fputs(indent, f);
    }
}

static int gravar_fragmento_catalogo(const char *path, const char *nome, const char *destino, char **blocos, int nblocos) {
    FILE *f=fopen(path,"wb");
    if (!f) return -1;
    fputs("{\n  \"tipo\": \"catalogo-mensagens-conector-sisc\",\n  \"versao\": 1,\n  \"conector\": ", f);
    json_escape_out(f,nome);
    fputs(",\n  \"destino\": ", f);
    json_escape_out(f,destino);
    fputs(",\n  \"origem\": \"web-api/catalogo-mensagens.json\",\n  \"mensagens\": [\n", f);
    for (int i=0;i<nblocos;i++) {
        escrever_bloco_indentado(f,blocos[i],"    ");
        fputs(i+1<nblocos ? ",\n" : "\n", f);
    }
    fputs("  ]\n}\n", f);
    fclose(f);
    chmod(path,0644);
    return 0;
}

static int preparar_fragmento_catalogo(const char *cwd, const char *tmp, const char *nome, char *rel_out, size_t relsz, int *n_out) {
    *n_out=0; if (rel_out && relsz) rel_out[0]='\0';

    char modular_rel[PATH_MAX*2]; snprintf(modular_rel,sizeof(modular_rel),"web-api/catalogo-%s.json",nome);
    char modular_path[PATH_MAX*2]; snprintf(modular_path,sizeof(modular_path),"%s/%s",cwd,modular_rel);
    if (arquivo_existe(modular_path)) {
        char *modular=ler_arquivo(modular_path);
        if(!modular) return -1;
        char destino_mod[PATH_MAX]; snprintf(destino_mod,sizeof(destino_mod),"conector__%s",nome);
        char **blocos=NULL; int n=0;
        int rc=coletar_blocos_catalogo_destino(modular,destino_mod,&blocos,&n);
        free(modular);
        liberar_blocos(blocos,n);
        if(rc!=0) return -1;
        char dst[PATH_MAX*2]; snprintf(dst,sizeof(dst),"%s/%s",tmp,modular_rel);
        if(copiar_arquivo(modular_path,dst,0644)!=0) return -1;
        snprintf(rel_out,relsz,"%s",modular_rel);
        *n_out=n;
        return 0;
    }

    char cat_path[PATH_MAX*2]; snprintf(cat_path,sizeof(cat_path),"%s/web-api/catalogo-mensagens.json",cwd);
    if (!arquivo_existe(cat_path)) return 0;
    char *catalogo=ler_arquivo(cat_path);
    if (!catalogo) return -1;
    char destino[PATH_MAX]; snprintf(destino,sizeof(destino),"conector__%s",nome);
    char **blocos=NULL; int n=0;
    int rc=coletar_blocos_catalogo_destino(catalogo,destino,&blocos,&n);
    free(catalogo);
    if (rc!=0) return -1;
    if (n<=0) { liberar_blocos(blocos,n); return 0; }
    snprintf(rel_out,relsz,"CATALOGO-MENSAGENS-CONECTOR.json");
    char dst[PATH_MAX*2]; snprintf(dst,sizeof(dst),"%s/%s",tmp,rel_out);
    rc=gravar_fragmento_catalogo(dst,nome,destino,blocos,n);
    liberar_blocos(blocos,n);
    if (rc!=0) return -1;
    *n_out=n;
    return 0;
}

static int rel_seguro(const char *rel) {
    if(!rel || !*rel || rel[0]=='/' || strstr(rel,"..")) return 0;
    if(strncmp(rel,"conectores/",11)==0) return 1;
    return strncmp(rel,"web-api/catalogo-",16)==0 && termina_com(rel,".json") && strcmp(rel,"web-api/catalogo-mensagens.json")!=0;
}

static int normalizar_rel_manifesto(const char *in, char *out, size_t outsz) {
    if (!in || !*in || !out || outsz==0) return -1;
    while (!strncmp(in,"./",2)) in += 2;
    if (!*in || in[0]=='/' || strstr(in,"..")) return -1;
    snprintf(out,outsz,"%s",in);
    size_t n=strlen(out);
    while (n>1 && out[n-1]=='/' && out[n-2]=='/') out[--n]='\0';
    if (!rel_seguro(out)) return -1;
    if (!strcmp(out,"conectores/") || !strcmp(out,"conectores")) return -1;
    if (strstr(out,"/secretos/") || termina_com(out,"/secretos") || strstr(out,"/secrets/") || termina_com(out,"/secrets")) return -1;
    return 0;
}

static int copiar_rel_recursivo(const char *cwd, const char *tmp, const char *rel, RelList *rels) {
    char rel_norm[PATH_MAX*2];
    if (normalizar_rel_manifesto(rel,rel_norm,sizeof(rel_norm))!=0) return -2;
    char rel_sem_barra[PATH_MAX*2]; snprintf(rel_sem_barra,sizeof(rel_sem_barra),"%s",rel_norm);
    size_t rn=strlen(rel_sem_barra);
    while (rn>0 && rel_sem_barra[rn-1]=='/') rel_sem_barra[--rn]='\0';

    char origem[PATH_MAX*3], destino[PATH_MAX*3];
    snprintf(origem,sizeof(origem),"%s/%s",cwd,rel_sem_barra);
    snprintf(destino,sizeof(destino),"%s/%s",tmp,rel_sem_barra);

    struct stat st;
    if (lstat(origem,&st)!=0) return -3;
    if (S_ISLNK(st.st_mode)) return -4;
    if (S_ISREG(st.st_mode)) {
        mode_t modo = (st.st_mode & 0111) ? 0755 : 0644;
        if (copiar_arquivo(origem,destino,modo)!=0) return -5;
        return rellist_add(rels,rel_sem_barra)==0 ? 1 : -6;
    }
    if (S_ISDIR(st.st_mode)) {
        DIR *d=opendir(origem);
        if (!d) return -7;
        if (mkdir_p(destino,0775)!=0) { closedir(d); return -8; }
        int copiados=0;
        struct dirent *e;
        while ((e=readdir(d)) != NULL) {
            if (!strcmp(e->d_name,".") || !strcmp(e->d_name,"..")) continue;
            char filho[PATH_MAX*3]; snprintf(filho,sizeof(filho),"%s/%s",rel_sem_barra,e->d_name);
            int r=copiar_rel_recursivo(cwd,tmp,filho,rels);
            if (r<0) { closedir(d); return r; }
            copiados += r;
        }
        closedir(d);
        return copiados;
    }
    return -9;
}

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 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); }
}

static int gravar_texto(const char *path, const char *txt, mode_t modo) {
    if (garantir_dir_pai(path,0775)!=0) return -1;
    FILE *f=fopen(path,"wb");
    if(!f) return -1;
    size_t n=strlen(txt?txt:"");
    int ok=fwrite(txt?txt:"",1,n,f)==n ? 0 : -1;
    if(fclose(f)!=0) ok=-1;
    chmod(path,modo);
    return ok;
}

static char *papel_declarado_manifesto(const char *manifesto, const char *rel) {
    if(!manifesto || !rel || !*rel) return NULL;
    const char *dep_ini=NULL,*dep_fim=NULL;
    if(localizar_objeto_json_chave(manifesto,"dependencias",&dep_ini,&dep_fim)!=0) return NULL;
    char *dep=substr_objeto_json(dep_ini,dep_fim);
    if(!dep) return NULL;
    const char *arr_ini=NULL,*arr_fim=NULL;
    char *out=NULL;
    if(localizar_array_json_chave(dep,"arquivos",&arr_ini,&arr_fim)==0) {
        const char *p=arr_ini+1;
        while(p<arr_fim) {
            while(p<arr_fim && (isspace((unsigned char)*p) || *p==',')) p++;
            if(p>=arr_fim || *p!='{') break;
            const char *of=json_composto_fim(p,'{','}',arr_fim);
            if(!of) break;
            char *obj=substr_objeto_json(p,of);
            if(!obj) break;
            char origem[PATH_MAX*2]="", destino[PATH_MAX*2]="", papel[128]="";
            extrair_string_json(obj,"origem",origem,sizeof(origem));
            if(!origem[0]) extrair_string_json(obj,"arquivo",origem,sizeof(origem));
            if(!origem[0]) extrair_string_json(obj,"caminho",origem,sizeof(origem));
            extrair_string_json(obj,"destino",destino,sizeof(destino));
            extrair_string_json(obj,"papel",papel,sizeof(papel));
            free(obj);
            char rel_norm[PATH_MAX*2];
            if(origem[0] && normalizar_rel_manifesto(origem,rel_norm,sizeof(rel_norm))==0 && !strcmp(rel_norm,rel)) {
                out=strdup(papel[0]?papel:"arquivo"); break;
            }
            if(destino[0] && normalizar_rel_manifesto(destino,rel_norm,sizeof(rel_norm))==0 && !strcmp(rel_norm,rel)) {
                out=strdup(papel[0]?papel:"arquivo"); break;
            }
            p=of+1;
        }
    }
    free(dep);
    return out;
}

static const char *papel_dependencia(const char *rel, const char *nome, const char *fmt_rel) {
    static char manifesto[PATH_MAX];
    snprintf(manifesto,sizeof(manifesto),"conectores/%s/%s.json",nome?nome:"",nome?nome:"");
    if(rel && !strcmp(rel,manifesto)) return "manifesto";
    if(rel && fmt_rel && !strcmp(rel,fmt_rel)) return "formato";
    if(rel && (!strcmp(rel,"CATALOGO-MENSAGENS-CONECTOR.json") || !strncmp(rel,"web-api/catalogo-",16))) return "catalogo-mensagens";
    if(rel && strstr(rel,"/handlers/")) return "handler";
    if(rel && strstr(rel,"/formatos/")) return "formato-extra";
    return "arquivo";
}

static char *montar_valor_dependencias(const char *manifesto, const char *nome, const char *fmt_rel, const char **rels, int nrels) {
    char *buf=NULL; size_t tam=0;
    FILE *f=open_memstream(&buf,&tam);
    if(!f) return NULL;
    fputs("{\n", f);
    fputs("        \"fonteSeguraImportacao\": true,\n", f);
    fputs("        \"arquivos\": [\n", f);
    for(int i=0;i<nrels;i++) {
        const char *rel=rels[i];
        int catalogo = rel && !strcmp(rel,"CATALOGO-MENSAGENS-CONECTOR.json");
        char *papel_user=papel_declarado_manifesto(manifesto,rel);
        const char *papel_final = papel_user ? papel_user : papel_dependencia(rel,nome,fmt_rel);
        fputs("            {\"papel\": ", f); json_escape_out(f,papel_final);
        free(papel_user);
        fputs(", \"origem\": ", f); json_escape_out(f,rel);
        fputs(", \"destino\": ", f); json_escape_out(f,catalogo ? "web-api/catalogo-mensagens.json" : rel);
        if(catalogo) fputs(", \"modo\": \"mesclar\", \"obrigatorio\": false", f);
        else fputs(", \"obrigatorio\": true", f);
        fputs("}", f);
        fputs(i+1<nrels ? ",\n" : "\n", f);
    }
    fputs("        ]\n    }", f);
    if(fclose(f)!=0){ free(buf); return NULL; }
    return buf;
}

static int localizar_valor_chave_json(const char *json, const char *chave, const char **ini, const char **fim) {
    char padrao[128]; snprintf(padrao,sizeof(padrao),"\"%s\"",chave);
    const char *p=strstr(json,padrao);
    if(!p) return -1;
    p += strlen(padrao);
    while(*p && isspace((unsigned char)*p)) p++;
    if(*p!=':') return -1;
    p++;
    while(*p && isspace((unsigned char)*p)) p++;
    if(!*p) return -1;
    *ini=p;
    if(*p=='{' || *p=='[') {
        const char *f=json_composto_fim(p,*p,*p=='{'?'}':']',NULL);
        if(!f) return -1;
        *fim=f; return 0;
    }
    if(*p=='\"') {
        int esc=0; p++;
        for(; *p; p++) {
            if(esc) esc=0;
            else if(*p=='\\') esc=1;
            else if(*p=='\"') { *fim=p; return 0; }
        }
        return -1;
    }
    const char *q=p;
    while(*q && *q!=',' && *q!='}' && *q!='\n' && *q!='\r') q++;
    while(q>p && isspace((unsigned char)q[-1])) q--;
    if(q<=p) return -1;
    *fim=q-1; return 0;
}

static char *substituir_trecho(const char *txt, const char *ini, const char *fim_excl, const char *novo) {
    size_t antes=(size_t)(ini-txt), meio=strlen(novo?novo:""), depois=strlen(fim_excl);
    char *out=malloc(antes+meio+depois+1);
    if(!out) return NULL;
    memcpy(out,txt,antes);
    memcpy(out+antes,novo?novo:"",meio);
    memcpy(out+antes+meio,fim_excl,depois+1);
    return out;
}

static int atualizar_dependencias_manifesto(const char *tmp, const char *manifesto_rel, const char *fmt_rel, const char *nome, const char **rels, int nrels, const char *manifesto_orig_path) {
    char path[PATH_MAX*3]; snprintf(path,sizeof(path),"%s/%s",tmp,manifesto_rel);
    char *json=ler_arquivo(path);
    if(!json) return -1;
    char *manifesto_orig = manifesto_orig_path ? ler_arquivo(manifesto_orig_path) : NULL;
    char *valor=montar_valor_dependencias(manifesto_orig?manifesto_orig:json,nome,fmt_rel,rels,nrels);
    free(manifesto_orig);
    if(!valor){ free(json); return -1; }
    char *novo=NULL;
    const char *vi=NULL,*vf=NULL;
    if(localizar_valor_chave_json(json,"dependencias",&vi,&vf)==0) {
        novo=substituir_trecho(json,vi,vf+1,valor);
    } else {
        char *prop=NULL; size_t psz=0;
        FILE *pf=open_memstream(&prop,&psz);
        if(!pf){ free(valor); free(json); return -1; }
        fputs("    \"dependencias\": ", pf);
        fputs(valor, pf);
        fputs(",\n", pf);
        if(fclose(pf)!=0){ free(prop); free(valor); free(json); return -1; }
        const char *pos=strstr(json,"\"descricao\"");
        if(pos) while(pos>json && pos[-1]!='\n') pos--;
        else {
            pos=strchr(json,'{');
            if(pos) pos++;
        }
        if(!pos) { free(prop); free(valor); free(json); return -1; }
        novo=substituir_trecho(json,pos,pos,prop);
        free(prop);
    }
    free(valor);
    int rc = novo ? gravar_texto(path,novo,0644) : -1;
    free(novo); free(json);
    return rc;
}

static int gravar_manifesto_pacote(const char *path, const char *nome, const char **rels, int nrels) {
    FILE *f=fopen(path,"wb");
    if(!f) return -1;
    time_t agora=time(NULL); struct tm tmv; gmtime_r(&agora,&tmv);
    char iso[32]; strftime(iso,sizeof(iso),"%Y-%m-%dT%H:%M:%SZ",&tmv);
    fprintf(f,"{\n  \"tipo\": \"pacote-conector-sisc\",\n  \"versao\": 1,\n  \"nome\": \"%s\",\n  \"criadoEm\": \"%s\",\n  \"arquivos\": [\n", nome, iso);
    for(int i=0;i<nrels;i++) fprintf(f,"    \"%s\"%s\n", rels[i], i+1<nrels ? "," : "");
    fputs("  ],\n  \"observacao\": \"Pacote autocontido; inclui extras declarados em arquivosPacote; secretos/ nao sao incluidos; fragmento de catalogo, quando presente, e mesclado na importacao.\"\n}\n", f);
    fclose(f); return 0;
}

int main(int argc, char **argv) {
    if(argc < 2 || !strcmp(argv[1],"--ajuda") || !strcmp(argv[1],"--help") || !strcmp(argv[1],"-h")) { uso(); return argc<2?1:0; }
    if(restricao_verificar("empacotar-conector") != 0) return 1;

    const char *nome = argv[1];
    const char *prefixo = "--empacotar-conector=";
    size_t prefixo_len = strlen(prefixo);
    if(strncmp(nome,prefixo,prefixo_len)==0) nome += prefixo_len;
    if(!nome_seguro(nome)) { fprintf(stderr,"empacotar-conector: nome invalido.\n"); return 1; }

    char cwd[PATH_MAX]; if(!getcwd(cwd,sizeof(cwd))){perror("getcwd");return 1;}
    char raiz_sisc[PATH_MAX]; if(raiz_sisc_por_executavel(raiz_sisc,sizeof(raiz_sisc))!=0){fprintf(stderr,"empacotar-conector: raiz SISC nao localizada.\n");return 1;}

    char manifesto_path[PATH_MAX*2]; snprintf(manifesto_path,sizeof(manifesto_path),"%s/conectores/%s/%s.json",cwd,nome,nome);
    int manifesto_legado = 0;
    if(!arquivo_existe(manifesto_path)) {
        snprintf(manifesto_path,sizeof(manifesto_path),"%s/conectores/%s.json",cwd,nome);
        manifesto_legado = 1;
    }
    if(!arquivo_existe(manifesto_path)){ fprintf(stderr,"empacotar-conector: manifesto nao encontrado: conectores/%s/%s.json\n",nome,nome); return 1; }
    char *manifesto=ler_arquivo(manifesto_path);
    if(!manifesto){ fprintf(stderr,"empacotar-conector: nao foi possivel ler manifesto.\n"); return 1; }

    char fmt_decl[PATH_MAX]="";
    int fr=extrair_string_json(manifesto,"formatoConector",fmt_decl,sizeof(fmt_decl));
    if(fr<0){ free(manifesto); fprintf(stderr,"empacotar-conector: formatoConector invalido.\n"); return 1; }
    char fmt_rel[PATH_MAX];
    if(fr==0 || !fmt_decl[0]) snprintf(fmt_rel,sizeof(fmt_rel),"conectores/%s/formatos/formato-%s.json",nome,nome);
    else {
        const char *f=fmt_decl;
        while(!strncmp(f,"./",2)) f+=2;
        if(strchr(f,'/')) snprintf(fmt_rel,sizeof(fmt_rel),"%s",f);
        else snprintf(fmt_rel,sizeof(fmt_rel),"conectores/%s/formatos/%s",nome,basename_seguro(f));
    }
    if(!rel_seguro(fmt_rel) || !termina_com(fmt_rel,".json")){ free(manifesto); fprintf(stderr,"empacotar-conector: formatoConector inseguro.\n"); return 1; }

    char handler_decl[PATH_MAX]="";
    int hr=extrair_string_json(manifesto,"handlerLerMensagem",handler_decl,sizeof(handler_decl));
    char handler_rel[PATH_MAX]="";
    if(hr > 0 && handler_decl[0]) {
        const char *h=handler_decl;
        while(!strncmp(h,"./",2)) h+=2;
        snprintf(handler_rel,sizeof(handler_rel),"%s",h);
        if(!rel_seguro(handler_rel) || !strstr(handler_rel,"/handlers/")) {
            free(manifesto); fprintf(stderr,"empacotar-conector: handlerLerMensagem fora do diretorio handlers do conector.\n"); return 1;
        }
    }
    char **extras=NULL; int nextras=0;
    if(coletar_arquivos_pacote_manifesto(manifesto,&extras,&nextras)!=0) {
        free(manifesto); fprintf(stderr,"empacotar-conector: arquivosPacote invalido.\n"); return 1;
    }
    free(manifesto);

    char fmt_abs[PATH_MAX*2]; snprintf(fmt_abs,sizeof(fmt_abs),"%s/%s",cwd,fmt_rel);
    if(!arquivo_existe(fmt_abs)){ stringlist_free(extras,nextras); fprintf(stderr,"empacotar-conector: formato nao encontrado: %s\n",fmt_rel); return 1; }
    char handler_abs[PATH_MAX*2]="";
    if(handler_rel[0]) {
        snprintf(handler_abs,sizeof(handler_abs),"%s/%s",cwd,handler_rel);
        if(!arquivo_existe(handler_abs)){ stringlist_free(extras,nextras); fprintf(stderr,"empacotar-conector: handler nao encontrado: %s\n",handler_rel); return 1; }
    }

    char tmp[PATH_MAX]; snprintf(tmp,sizeof(tmp),"/tmp/sisc_pacote_conector_%ld_%s",(long)getpid(),nome);
    rm_rf(tmp);
    if(mkdir_p(tmp,0775)!=0){ stringlist_free(extras,nextras); perror("empacotar-conector: tmp"); return 1; }

    RelList rels; rellist_init(&rels);
    char con_rel[PATH_MAX];
    if (manifesto_legado) snprintf(con_rel,sizeof(con_rel),"conectores/%s.json",nome);
    else snprintf(con_rel,sizeof(con_rel),"conectores/%s/%s.json",nome,nome);
    char dst[PATH_MAX*2];
    snprintf(dst,sizeof(dst),"%s/%s",tmp,con_rel);
    if(copiar_arquivo(manifesto_path,dst,0644)!=0 || rellist_add(&rels,con_rel)!=0){rellist_free(&rels);stringlist_free(extras,nextras);rm_rf(tmp);fprintf(stderr,"empacotar-conector: falha copiando manifesto.\n");return 1;}
    if(handler_rel[0]) {
        snprintf(dst,sizeof(dst),"%s/%s",tmp,handler_rel);
        if(copiar_arquivo(handler_abs,dst,0755)!=0 || rellist_add(&rels,handler_rel)!=0){rellist_free(&rels);stringlist_free(extras,nextras);rm_rf(tmp);fprintf(stderr,"empacotar-conector: falha copiando handler.\n");return 1;}
    }
    snprintf(dst,sizeof(dst),"%s/%s",tmp,fmt_rel);
    if(copiar_arquivo(fmt_abs,dst,0644)!=0 || rellist_add(&rels,fmt_rel)!=0){rellist_free(&rels);stringlist_free(extras,nextras);rm_rf(tmp);fprintf(stderr,"empacotar-conector: falha copiando formato.\n");return 1;}
    int extras_copiados=0;
    for(int i=0;i<nextras;i++) {
        if(rellist_contem(&rels,extras[i])) continue;
        int r=copiar_rel_recursivo(cwd,tmp,extras[i],&rels);
        if(r<0){rellist_free(&rels);stringlist_free(extras,nextras);rm_rf(tmp);fprintf(stderr,"empacotar-conector: arquivo extra invalido/ausente em arquivosPacote/dependencias: %s\n",extras[i]);return 1;}
        extras_copiados += r;
    }
    stringlist_free(extras,nextras);
    char catalogo_rel[PATH_MAX]=""; int catalogo_msgs=0;
    if(preparar_fragmento_catalogo(cwd,tmp,nome,catalogo_rel,sizeof(catalogo_rel),&catalogo_msgs)!=0){rellist_free(&rels);rm_rf(tmp);fprintf(stderr,"empacotar-conector: falha lendo bloco do catalogo web-api.\n");return 1;}
    if(catalogo_msgs>0 && rellist_add(&rels,catalogo_rel)!=0){rellist_free(&rels);rm_rf(tmp);fprintf(stderr,"empacotar-conector: falha registrando catalogo no pacote.\n");return 1;}
    if(atualizar_dependencias_manifesto(tmp,con_rel,fmt_rel,nome,(const char**)rels.v,rels.n,manifesto_path)!=0){rellist_free(&rels);rm_rf(tmp);fprintf(stderr,"empacotar-conector: falha atualizando dependencias no manifesto do pacote.\n");return 1;}
    snprintf(dst,sizeof(dst),"%s/PACOTE-CONECTOR.json",tmp); if(gravar_manifesto_pacote(dst,nome,(const char**)rels.v,rels.n)!=0){rellist_free(&rels);rm_rf(tmp);fprintf(stderr,"empacotar-conector: falha gravando manifesto do pacote.\n");return 1;}

    char bib_dir[PATH_MAX*2]; snprintf(bib_dir,sizeof(bib_dir),"%s/core/biblioteca-conectores",raiz_sisc);
    if(mkdir_p(bib_dir,0775)!=0){rm_rf(tmp);fprintf(stderr,"empacotar-conector: nao foi possivel criar core/biblioteca-conectores.\n");return 1;}
    char pacote[PATH_MAX*2]; snprintf(pacote,sizeof(pacote),"%s/%s.tar.gz",bib_dir,nome);
    char qtmp[PATH_MAX*4], qpkg[PATH_MAX*4], cmd[PATH_MAX*10];
    if(shell_quote(tmp,qtmp,sizeof(qtmp))!=0 || shell_quote(pacote,qpkg,sizeof(qpkg))!=0){rm_rf(tmp);return 1;}
    snprintf(cmd,sizeof(cmd),"tar -czf %s -C %s .",qpkg,qtmp);
    int rc=system(cmd);
    rm_rf(tmp);
    rellist_free(&rels);
    if(rc!=0){ fprintf(stderr,"empacotar-conector: tar falhou.\n"); return 1; }
    printf("Pacote criado: core/biblioteca-conectores/%s.tar.gz\n", nome);
    printf("Arquivos extras incluidos via arquivosPacote/dependencias: %d\n", extras_copiados);
    if(catalogo_msgs>0) printf("Bloco de catalogo incluido: %d mensagem(ns) para conector__%s\n", catalogo_msgs, nome);
    else printf("Bloco de catalogo incluido: nenhum encontrado para conector__%s\n", nome);
    return 0;
}
