/*
 * importar-spec - Importa specs da biblioteca central para o sistema atual
 * Uso: pp --importar-spec[=<spec_arquivo.json>]
 *
 * Sem valor, copia todos os arquivos JSON da origem. Com valor, copia apenas
 * a spec informada.
 */

#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];
} Spec;

static void uso(void)
{
    fputs(
        "Uso:\n"
        "  pp --importar-spec\n"
        "  pp --importar-spec=<spec_arquivo.json>\n"
        "  pp --importar-spec --ajuda:<grupo>\n"
        "  pp --importar-spec --ajuda=<grupo>\n"
        "\n"
        "Descrição:\n"
        "  Importa specs da biblioteca central do SISC para o sistema atual.\n"
        "  Sem valor informado, importa todos os arquivos da origem.\n"
        "  Com valor informado, importa somente a spec indicada.\n"
        "\n"
        "Grupos de ajuda: uso, fonte, selecao, arquivos, exemplos.\n"
        "\n"
        "1) Uso e ajuda [uso ajuda]\n"
        "  --ajuda, --help, -h            Exibe esta ajuda\n"
        "  --ajuda:<grupo>, --ajuda=<grupo>  Filtra um grupo via pp. Ex: pp --importar-spec --ajuda:selecao\n"
        "\n"
        "2) Fonte [fonte entrada]\n"
        "  Fonte fixa: <raiz_sisc>/core/biblioteca-specs/.\n"
        "  --importar-spec=<arquivo>      Spec da biblioteca; aceita com ou sem .json\n"
        "  Exemplo com todas as flags do grupo:\n"
        "    pp --importar-spec=spec_seguranca.json\n"
        "\n"
        "3) Seleção [selecao]\n"
        "  Sem valor informado, importa todas as specs da origem.\n"
        "  Com valor informado, importa somente a spec indicada.\n"
        "\n"
        "4) Arquivos copiados [arquivos]\n"
        "  Lista somente spec_*.json e diretiva-*.json; ignora spec_modelo.json.\n"
        "  Destino: spec-diretivas/ no sistema atual; o diretório é criado se faltar.\n"
        "  Se o arquivo já existir, ele é sobrescrito.\n"
        "\n"
        "5) Exemplos [exemplos]\n"
        "  pp --importar-spec\n"
        "  pp --importar-spec=spec_seguranca_zero_trust_web_api.json\n"
        "  pp --importar-spec=seguranca_zero_trust_web_api\n",
        stdout);
}

static void resolver_dir_biblioteca(const char *raiz_sisc, char *out, size_t tam)
{
    snprintf(out, tam, "%s/core/biblioteca-specs", raiz_sisc);
}

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 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 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 cmp_spec(const void *a, const void *b)
{
    const Spec *ca = (const Spec *)a, *cb = (const Spec *)b;
    return strcasecmp(ca->nome, cb->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))) {
        const char *n = e->d_name;
        /* Lista specs (spec_*.json) e diretivas (diretiva-*.json), ignora spec_modelo.json */
        if (n[0] == '.' || !termina_com(n, ".json")) continue;
        if (strncmp(n, "spec_", 5) != 0 && strncmp(n, "diretiva-", 9) != 0) continue;
        if (strcmp(n, "spec_modelo.json") == 0) 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 char *ler_arquivo(const char *p)
{
    FILE *fp = fopen(p, "rb");
    if (!fp) return NULL;
    fseek(fp, 0, SEEK_END);
    long t = ftell(fp);
    if (t < 0 || t > 20 * 1024 * 1024) { fclose(fp); return NULL; }
    fseek(fp, 0, SEEK_SET);
    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 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);
    size_t n = fwrite(conteudo, 1, len, fp);
    if (len == 0 || conteudo[len - 1] != '\n') fputc('\n', fp);
    fclose(fp);
    chmod(p, modo);
    return n == len ? 0 : -1;
}

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

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

int main(int argc, char **argv)
{
    if (restricao_verificar("importar-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, "importar-spec: nome de spec invalido.\n");
        return 1;
    }

    /* Obtem raiz do sisc (pai do diretorio atual) */
    char cwd[PATH_MAX];
    if (!getcwd(cwd, sizeof(cwd))) { perror("getcwd"); return 1; }
    /* Diretorio atual deve ser subdiretorio direto de sisc */
    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, "importar-spec: nao foi possivel determinar raiz sisc.\n");
        return 1;
    }
    *slash = '\0';

    /* Localiza a biblioteca central. */
    char src_specs[PATH_MAX * 2];
    resolver_dir_biblioteca(raiz_sisc, src_specs, sizeof(src_specs));

    /* Prepara diretorio de destino: sempre spec-diretivas/ na raiz do sistema atual */
    char dst_specs[PATH_MAX * 2];
    snprintf(dst_specs, sizeof(dst_specs), "%s/spec-diretivas", cwd);

    if (garantir_dir(dst_specs, 0775) != 0) {
        fprintf(stderr, "importar-spec: nao foi possivel criar %s\n", dst_specs);
        return 1;
    }

    if (!sem_valor) {
        char src[PATH_MAX * 2], dst[PATH_MAX * 2];
        snprintf(src, sizeof(src), "%s/%s", src_specs, arquivo);
        snprintf(dst, sizeof(dst), "%s/%s", dst_specs, arquivo);

        struct stat st;
        if (stat(src, &st) != 0 || !S_ISREG(st.st_mode)) {
            fprintf(stderr, "importar-spec: spec nao encontrada em core/biblioteca-specs/: %s\n", arquivo);
            return 1;
        }

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

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

    /* Sem valor: lista e importa todas as specs da biblioteca central. */
    Spec *specs = NULL;
    int total = 0;
    if (listar_specs(src_specs, &specs, &total) != 0 || total == 0) {
        fprintf(stderr, "importar-spec: nenhuma spec-diretiva encontrada em %s.\n", src_specs);
        free(specs);
        return 1;
    }

    printf("Fonte: biblioteca central\n");
    printf("Sem valor informado: importando todas as spec-diretivas da origem.\n");
    printf("\nImportando %d spec(s)...\n\n", total);

    int importados = 0, sobrescritos = 0, erros = 0;

    for (int i = 0; i < total; i++) {
        Spec *s = &specs[i];

        printf("[%d/%d] %s\n", i + 1, total, s->nome);

        char dst_json[PATH_MAX * 2];
        snprintf(dst_json, sizeof(dst_json), "%s/%s", dst_specs, s->arquivo);

        int ja_existia = access(dst_json, F_OK) == 0;

        if (copiar_arquivo(s->caminho, dst_json, 0644) != 0) {
            fprintf(stderr, "  [erro] nao foi possivel copiar %s\n", s->arquivo);
            erros++;
            continue;
        }
        printf("  [ok] spec-diretivas/%s%s\n", s->arquivo, ja_existia ? " (sobrescrito)" : "");
        importados++;
        if (ja_existia) sobrescritos++;
    }

    printf("\nImportacao concluida.\n");
    printf("Spec-diretivas importadas: %d\n", importados);
    printf("Spec-diretivas sobrescritas: %d\n", sobrescritos);
    if (erros > 0) printf("Erros: %d\n", erros);

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