/*
 * dif-spec - Exibe diferencas entre spec local e spec da biblioteca central
 * Uso: pp --dif-spec=<spec_arquivo.json>
 *
 * Compara:
 *   <sistema>/spec-diretivas/<spec>
 *   <raiz_sisc>/core/biblioteca-specs/<spec>
 */

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

#define COR_RESET "\033[0m"
#define COR_VERDE "\033[32m"
#define COR_VERMELHO "\033[31m"
#define COR_CIAN "\033[36m"
#define COR_NEGRITO "\033[1m"

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

static void uso(void)
{
    const char *cmd = base_name("dif-spec");
    (void)cmd;
    fputs(
        "Uso:\n"
        "  pp --dif-spec=<spec_arquivo.json>\n"
        "  pp --dif-spec --ajuda:<grupo>\n"
        "\n"
        "Descrição:\n"
        "  Exibe, em formato unificado e legível, as diferenças entre a spec local\n"
        "  em spec-diretivas/ e a mesma spec na biblioteca core/biblioteca-specs/.\n"
        "\n"
        "Grupos de ajuda: uso, entrada, comparacao, exemplos.\n"
        "\n"
        "1) Uso e ajuda [uso ajuda]\n"
        "  --ajuda, --help, -h            Exibe esta ajuda\n"
        "  --ajuda:<grupo>                Filtra grupo via pp\n"
        "\n"
        "2) Entrada [entrada spec]\n"
        "  --dif-spec=<arquivo>           Nome da spec; aceita com ou sem .json\n"
        "  Aceita spec_x.json, spec_x ou x.\n"
        "\n"
        "3) Comparação [comparacao diferencas]\n"
        "  Origem local: spec-diretivas/<arquivo>.\n"
        "  Referência: core/biblioteca-specs/<arquivo>.\n"
        "  Legenda do diff: '-' mostra a linha local; '+' mostra a linha da biblioteca.\n"
        "\n"
        "4) Exemplos [exemplos]\n"
        "  pp --dif-spec=spec_seguranca.json\n"
        "  pp --dif-spec=seguranca\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 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 (!strncmp(v, "core/biblioteca-specs/", 23)) v += 23;
    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;
        return snprintf(out, tam, "%s.json", d) > 0 && strlen(out) < tam ? 0 : -1;
    }

    char s[256];
    slug(v, s, sizeof(s));
    if (!s[0]) return -1;
    if (!strncmp(s, "spec_", 5)) return snprintf(out, tam, "%s.json", s) > 0 && strlen(out) < tam ? 0 : -1;
    return snprintf(out, tam, "spec_%s.json", s) > 0 && strlen(out) < tam ? 0 : -1;
}

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

static int arquivos_iguais(const char *a, const char *b)
{
    FILE *fa = fopen(a, "rb");
    if (!fa) return 0;
    FILE *fb = fopen(b, "rb");
    if (!fb) { fclose(fa); return 0; }
    int igual = 1;
    unsigned char ba[8192], bb[8192];
    for (;;) {
        size_t na = fread(ba, 1, sizeof(ba), fa);
        size_t nb = fread(bb, 1, sizeof(bb), fb);
        if (na != nb || memcmp(ba, bb, na) != 0) { igual = 0; break; }
        if (na < sizeof(ba)) {
            if (ferror(fa) || ferror(fb)) igual = 0;
            break;
        }
    }
    fclose(fa); fclose(fb);
    return igual;
}

static void imprimir_linha_colorida(const char *linha, int cor, int *adds, int *dels)
{
    if (!linha) return;
    int is_header = !strncmp(linha, "---", 3) || !strncmp(linha, "+++", 3);
    if (linha[0] == '@') {
        if (cor) fputs(COR_CIAN, stdout);
        fputs(linha, stdout);
        if (cor) fputs(COR_RESET, stdout);
    } else if (linha[0] == '+' && !is_header) {
        if (adds) (*adds)++;
        if (cor) fputs(COR_VERDE, stdout);
        fputs(linha, stdout);
        if (cor) fputs(COR_RESET, stdout);
    } else if (linha[0] == '-' && !is_header) {
        if (dels) (*dels)++;
        if (cor) fputs(COR_VERMELHO, stdout);
        fputs(linha, stdout);
        if (cor) fputs(COR_RESET, stdout);
    } else if (is_header) {
        if (cor) fputs(COR_NEGRITO, stdout);
        fputs(linha, stdout);
        if (cor) fputs(COR_RESET, stdout);
    } else {
        fputs(linha, stdout);
    }
    size_t n = strlen(linha);
    if (n == 0 || linha[n - 1] != '\n') fputc('\n', stdout);
}

static int executar_diff(const char *local, const char *bib, const char *label_local, const char *label_bib)
{
    int fd[2];
    if (pipe(fd) != 0) { perror("dif-spec: pipe"); return 1; }
    pid_t pid = fork();
    if (pid < 0) { perror("dif-spec: fork"); close(fd[0]); close(fd[1]); return 1; }
    if (pid == 0) {
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        dup2(fd[1], STDERR_FILENO);
        close(fd[1]);
        execlp("diff", "diff", "-u", "-L", label_local, "-L", label_bib, local, bib, (char *)NULL);
        perror("dif-spec: exec diff");
        _exit(127);
    }
    close(fd[1]);

    FILE *fp = fdopen(fd[0], "r");
    if (!fp) { close(fd[0]); return 1; }
    char *linha = NULL;
    size_t cap = 0;
    int adds = 0, dels = 0;
    int cor = isatty(STDOUT_FILENO);
    while (getline(&linha, &cap, fp) != -1) imprimir_linha_colorida(linha, cor, &adds, &dels);
    free(linha);
    fclose(fp);

    int st = 0;
    waitpid(pid, &st, 0);
    if (WIFEXITED(st)) {
        int rc = WEXITSTATUS(st);
        if (rc == 0 || rc == 1) {
            printf("\nResumo: %d linha(s) da biblioteca adicionada(s), %d linha(s) locais removida(s).\n", adds, dels);
            return 0;
        }
    }
    fprintf(stderr, "dif-spec: falha ao executar diff.\n");
    return 1;
}

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

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

    char arquivo[PATH_MAX];
    if (nome_arquivo_spec(argv[1], arquivo, sizeof(arquivo)) != 0) {
        fprintf(stderr, "dif-spec: nome de spec invalido.\n");
        return 1;
    }

    char cwd[PATH_MAX];
    if (!getcwd(cwd, sizeof(cwd))) { perror("dif-spec: getcwd"); 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, "dif-spec: nao foi possivel determinar raiz SISC.\n");
        return 1;
    }
    *slash = '\0';

    char local[PATH_MAX * 2], bib[PATH_MAX * 2];
    snprintf(local, sizeof(local), "%s/spec-diretivas/%s", cwd, arquivo);
    snprintf(bib, sizeof(bib), "%s/core/biblioteca-specs/%s", raiz_sisc, arquivo);

    printf("=== Diferenças de spec ===\n");
    printf("Spec:       %s\n", arquivo);
    printf("Local:      spec-diretivas/%s\n", arquivo);
    printf("Biblioteca: core/biblioteca-specs/%s\n", arquivo);
    printf("Legenda:    '-' = local; '+' = biblioteca\n\n");
    fflush(stdout);

    if (!eh_arq(local)) {
        fprintf(stderr, "dif-spec: spec local nao encontrada: spec-diretivas/%s\n", arquivo);
        return 1;
    }
    if (!eh_arq(bib)) {
        fprintf(stderr, "dif-spec: spec da biblioteca nao encontrada: core/biblioteca-specs/%s\n", arquivo);
        return 1;
    }

    if (arquivos_iguais(local, bib)) {
        printf("Status: iguais. Nenhuma diferença encontrada.\n");
        return 0;
    }

    printf("Status: diferentes.\n\n");
    char label_local[PATH_MAX], label_bib[PATH_MAX];
    snprintf(label_local, sizeof(label_local), "spec-diretivas/%s", arquivo);
    snprintf(label_bib, sizeof(label_bib), "core/biblioteca-specs/%s", arquivo);
    return executar_diff(local, bib, label_local, label_bib);
}
