/*
 * exportar-spec - Exporta specs do sistema atual para a biblioteca central
 * Uso: pp --exportar-spec[=<spec_arquivo.json>]
 *
 * Origem fixa:  spec-diretivas/ no sistema atual.
 * Destino fixo: core/biblioteca-specs/ na raiz SISC.
 * Se o arquivo ja existir na biblioteca, ele e sobrescrito.
 */

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

typedef struct {
    char nome[PATH_MAX];
    char arquivo[PATH_MAX];
    char caminho[PATH_MAX * 2];
} Spec;

static void uso(void)
{
    fputs(
        "Uso:\n"
        "  pp --exportar-spec\n"
        "  pp --exportar-spec=<spec_arquivo.json>\n"
        "  pp --exportar-spec --ajuda:<grupo>\n"
        "\n"
        "Descrição:\n"
        "  Exporta specs de spec-diretivas/ do sistema atual para a biblioteca\n"
        "  central core/biblioteca-specs/. A origem e o destino sao fixos.\n"
        "  Sem valor informado, exporta todos os arquivos da origem.\n"
        "\n"
        "Grupos de ajuda: uso, origem, destino, exemplos.\n"
        "\n"
        "1) Uso e ajuda [uso ajuda]\n"
        "  --ajuda, --help, -h            Exibe esta ajuda\n"
        "  --ajuda:<grupo>                Filtra um grupo via pp\n"
        "\n"
        "2) Origem [origem entrada]\n"
        "  --exportar-spec                Exporta todas as specs em spec-diretivas/.\n"
        "  --exportar-spec=<arquivo>      Exporta uma spec local; aceita com ou sem .json\n"
        "  Origem fixa: spec-diretivas/<arquivo> no sistema atual.\n"
        "\n"
        "3) Destino [destino biblioteca]\n"
        "  Destino fixo: core/biblioteca-specs/<arquivo>.\n"
        "  Se o arquivo ja existir na biblioteca, ele e sobrescrito.\n"
        "\n"
        "4) Exemplos [exemplos]\n"
        "  pp --exportar-spec\n"
        "  pp --exportar-spec=spec_seguranca_zero_trust_web_api.json\n"
        "  pp --exportar-spec=seguranca_zero_trust_web_api\n",
        stdout);
}

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

static int nome_arquivo_copiavel(const char *n)
{
    if (!n || n[0] == '.' || !termina_com_ci(n, ".json")) return 0;
    if (strcasecmp(n, "spec_modelo.json") == 0) return 0;
    return strncmp(n, "spec_", 5) == 0 || strncmp(n, "diretiva-", 9) == 0;
}

static int cmp_spec(const void *a, const void *b)
{
    const Spec *sa = (const Spec *)a, *sb = (const Spec *)b;
    return strcasecmp(sa->nome, sb->nome);
}

static int listar_specs(const char *dir_specs, Spec **out, int *total_out)
{
    DIR *d = opendir(dir_specs);
    if (!d) return -1;

    int cap = 32, total = 0;
    Spec *v = calloc((size_t)cap, sizeof(Spec));
    if (!v) { closedir(d); return -1; }

    struct dirent *e;
    while ((e = readdir(d)) != NULL) {
        const char *n = e->d_name;
        if (!nome_arquivo_copiavel(n)) continue;

        char path[PATH_MAX * 2];
        snprintf(path, sizeof(path), "%s/%s", dir_specs, n);
        struct stat st;
        if (stat(path, &st) != 0 || !S_ISREG(st.st_mode)) continue;

        if (total >= cap) {
            cap *= 2;
            Spec *nv = realloc(v, (size_t)cap * sizeof(Spec));
            if (!nv) { free(v); closedir(d); return -1; }
            v = nv;
        }
        snprintf(v[total].arquivo, sizeof(v[total].arquivo), "%s", n);
        snprintf(v[total].caminho, sizeof(v[total].caminho), "%s", path);
        snprintf(v[total].nome, sizeof(v[total].nome), "%s", n);
        v[total].nome[strlen(v[total].nome) - 5] = '\0';
        total++;
    }

    closedir(d);
    qsort(v, (size_t)total, sizeof(Spec), cmp_spec);
    *out = v;
    *total_out = total;
    return 0;
}

static void slug(const char *in, char *out, size_t tam)
{
    size_t j = 0;
    int sep = 0;
    if (!out || tam == 0) return;
    out[0] = '\0';
    if (!in) return;
    while (*in && isspace((unsigned char)*in)) in++;
    for (size_t i = 0; in[i] && j + 1 < tam; i++) {
        unsigned char c = (unsigned char)in[i];
        if (isalnum(c)) { out[j++] = (char)tolower(c); sep = 0; }
        else if (c == '-' || c == '_' || isspace(c)) {
            if (!sep && j > 0) out[j++] = '_';
            sep = 1;
        }
    }
    while (j > 0 && out[j - 1] == '_') j--;
    out[j] = '\0';
}

static int nome_arquivo_spec(const char *arg, char *out, size_t tam)
{
    char tmp[PATH_MAX];
    if (!arg || !*arg || !out || tam == 0) return -1;
    snprintf(tmp, sizeof(tmp), "%s", arg);
    char *v = tmp;
    while (isspace((unsigned char)*v)) v++;
    if (!strncmp(v, "spec-diretivas/", 15)) v += 15;
    if (strchr(v, '/') || strchr(v, '\\') || strstr(v, "..")) return -1;
    size_t len = strlen(v);
    while (len > 0 && isspace((unsigned char)v[len - 1])) v[--len] = '\0';
    if (len == 0) return -1;
    if (termina_com_ci(v, ".json")) v[strlen(v) - 5] = '\0';

    if (!strncmp(v, "diretiva-", 9)) {
        char d[256];
        size_t j = 0;
        for (size_t i = 0; v[i] && j + 1 < sizeof(d); i++) {
            unsigned char c = (unsigned char)v[i];
            if (isalnum(c) || c == '-' || c == '_') d[j++] = (char)tolower(c);
            else return -1;
        }
        d[j] = '\0';
        if (j == 0) return -1;
        if (snprintf(out, tam, "%s.json", d) < 0 || strlen(out) >= tam) return -1;
        return 0;
    }

    char s[256];
    slug(v, s, sizeof(s));
    if (!s[0]) return -1;

    if (!strncmp(s, "spec_", 5)) {
        if (snprintf(out, tam, "%s.json", s) < 0 || strlen(out) >= tam) return -1;
    } else {
        if (snprintf(out, tam, "spec_%s.json", s) < 0 || strlen(out) >= tam) return -1;
    }
    return 0;
}

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

static int eh_arq(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);
    int erro = ferror(fp);
    fclose(fp);
    if (erro) { free(b); return NULL; }
    b[n] = '\0';
    return b;
}

static int escrever_arquivo(const char *p, const char *conteudo, mode_t modo)
{
    FILE *fp = fopen(p, "wb");
    if (!fp) return -1;
    size_t len = strlen(conteudo ? conteudo : "");
    if (len > 0 && fwrite(conteudo, 1, len, fp) != len) { fclose(fp); return -1; }
    if (len == 0 || conteudo[len - 1] != '\n') fputc('\n', fp);
    if (fclose(fp) != 0) return -1;
    chmod(p, modo);
    return 0;
}

static int copiar_arquivo(const char *origem, const char *destino, mode_t modo)
{
    char *conteudo = ler_arquivo(origem);
    if (!conteudo) return -1;
    int r = escrever_arquivo(destino, conteudo, modo);
    free(conteudo);
    return r;
}

int main(int argc, char **argv)
{
    if (restricao_verificar("exportar-spec") != 0) return 1;

    if (argc >= 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "--ajuda") || !strcmp(argv[1], "-h"))) {
        uso();
        return 0;
    }

    int sem_valor = (argc < 2 || argv[1][0] == '\0');
    char arquivo[PATH_MAX];
    arquivo[0] = '\0';
    if (!sem_valor && nome_arquivo_spec(argv[1], arquivo, sizeof(arquivo)) != 0) {
        fprintf(stderr, "exportar-spec: nome de spec invalido.\n");
        return 1;
    }

    char cwd[PATH_MAX];
    if (!getcwd(cwd, sizeof(cwd))) { perror("exportar-spec: getcwd"); return 1; }

    char src_dir[PATH_MAX * 2];
    snprintf(src_dir, sizeof(src_dir), "%s/spec-diretivas", cwd);
    if (!eh_dir(src_dir)) {
        fprintf(stderr, "exportar-spec: diretorio spec-diretivas/ nao encontrado no sistema atual.\n");
        return 1;
    }

    char raiz_sisc[PATH_MAX];
    snprintf(raiz_sisc, sizeof(raiz_sisc), "%s", cwd);
    char *slash = strrchr(raiz_sisc, '/');
    if (!slash || slash == raiz_sisc) {
        fprintf(stderr, "exportar-spec: nao foi possivel determinar raiz SISC.\n");
        return 1;
    }
    *slash = '\0';

    char dst_dir[PATH_MAX * 2];
    snprintf(dst_dir, sizeof(dst_dir), "%s/core/biblioteca-specs", raiz_sisc);
    if (!eh_dir(dst_dir)) {
        fprintf(stderr, "exportar-spec: biblioteca central nao encontrada: core/biblioteca-specs/.\n");
        return 1;
    }

    if (sem_valor) {
        Spec *specs = NULL;
        int total = 0;
        if (listar_specs(src_dir, &specs, &total) != 0 || total == 0) {
            fprintf(stderr, "exportar-spec: nenhuma spec-diretiva encontrada em spec-diretivas/.\n");
            free(specs);
            return 1;
        }

        printf("Sem valor informado: exportando todas as spec-diretivas da origem.\n");
        printf("\nExportando %d spec(s)...\n\n", total);

        int exportados = 0, sobrescritos = 0, erros = 0;
        for (int i = 0; i < total; i++) {
            char dst[PATH_MAX * 2];
            snprintf(dst, sizeof(dst), "%s/%s", dst_dir, specs[i].arquivo);

            int ja_existia = eh_arq(dst);
            printf("[%d/%d] %s\n", i + 1, total, specs[i].nome);
            if (copiar_arquivo(specs[i].caminho, dst, 0644) != 0) {
                fprintf(stderr, "  [erro] nao foi possivel copiar %s\n", specs[i].arquivo);
                erros++;
                continue;
            }
            printf("  [ok] core/biblioteca-specs/%s%s\n", specs[i].arquivo, ja_existia ? " (sobrescrito)" : "");
            exportados++;
            if (ja_existia) sobrescritos++;
        }

        printf("\nExportacao concluida.\n");
        printf("Spec-diretivas exportadas: %d\n", exportados);
        printf("Spec-diretivas sobrescritas: %d\n", sobrescritos);
        if (erros > 0) printf("Erros: %d\n", erros);

        free(specs);
        return erros > 0 ? 1 : 0;
    }

    char src[PATH_MAX * 2], dst[PATH_MAX * 2];
    snprintf(src, sizeof(src), "%s/%s", src_dir, arquivo);
    snprintf(dst, sizeof(dst), "%s/%s", dst_dir, arquivo);

    if (!eh_arq(src)) {
        fprintf(stderr, "exportar-spec: spec nao encontrada em spec-diretivas/: %s\n", arquivo);
        return 1;
    }

    int ja_existia = eh_arq(dst);
    if (copiar_arquivo(src, dst, 0644) != 0) {
        fprintf(stderr, "exportar-spec: erro ao copiar spec para core/biblioteca-specs/%s\n", arquivo);
        return 1;
    }

    printf("Spec exportada: spec-diretivas/%s -> core/biblioteca-specs/%s%s\n",
           arquivo, arquivo, ja_existia ? " (sobrescrito)" : "");
    return 0;
}
