/*
 * kk-push-github - Inicializa (se necessário), comita e envia a pasta atual para o GitHub.
 * Uso:
 *   kk --push-github=<conta> [--emitir-ssh] [--commit='mensagem']
 *   kk --push-github=<conta> --base '<base> <usuario> <senha>' [--commit='mensagem']
 *
 * A pasta em que o comando é executado é sempre tratada como raiz do repositório.
 * Com --emitir-ssh, apenas garante a chave SSH em /etc/secretos/tokens-sistema e imprime a chave pública.
 */

#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

#define DIR_SECRETOS "/etc/secretos/tokens-sistema"

static void uso(void)
{
    fputs(
        "Uso:\n"
        "  kk --push-github=<conta> [--emitir-ssh] [--commit='mensagem']\n"
        "  kk --push-github=<conta> --base '<base> <usuario> <senha>' [--commit='mensagem']\n"
        "  kk --push-github --ajuda:<grupo>\n"
        "\n"
        "Descrição:\n"
        "  Inicializa quando necessário, commita e envia a pasta atual para o GitHub.\n"
        "  A pasta de execução é tratada como raiz do repositório.\n"
        "\n"
        "Grupos de ajuda: uso, entrada, opcoes, processo, exemplos.\n"
        "\n"
        "1) Uso e ajuda [uso ajuda]\n"
        "  --ajuda, --help, -h            Exibe esta ajuda\n"
        "  --ajuda:<grupo>                Filtra um grupo. Ex: kk --push-github --ajuda:opcoes\n"
        "\n"
        "2) Entrada [entrada]\n"
        "  --push-github=<conta>          Conta GitHub; use letras, números e hífen\n"
        "\n"
        "3) Opções [opcoes]\n"
        "  --emitir-ssh                   Apenas gera/exibe chave SSH pública\n"
        "  --commit='mensagem'            Mensagem de commit; padrão atualização automática\n"
        "  --commit mensagem              Forma alternativa aceita\n"
        "  --base '<base> <usuario> <senha>' Exporta a base MySQL/MariaDB para sql/<base>.sql antes do git add\n"
        "  Exemplo com todas as flags do grupo (--emitir-ssh é modo isolado; --commit complementa push normal):\n"
        "    kk --push-github=costarrear --commit='correcao de bug'  # push normal\n"
        "    kk --push-github=costarrear --base 'condominio admin aapl'  # gera sql/condominio.sql e envia junto\n"
        "    kk --push-github=costarrear --emitir-ssh  # apenas gera/exibe chave SSH\n"
        "\n"
        "4) Processo [processo]\n"
        "  Executa git init quando necessário, configura usuário, garante chave SSH em\n"
        "  /etc/secretos/tokens-sistema, opcionalmente exporta a base indicada, adiciona origin,\n"
        "  roda git add -A, git commit e git push.\n"
        "\n"
        "5) Exemplos [exemplos]\n"
        "  kk --push-github=costarrear\n"
        "  kk --push-github=costarrear --commit='correcao de bug'\n"
        "  kk --push-github=costarrear --base 'condominio admin aapl'\n"
        "  kk --push-github=costarrear --emitir-ssh\n",
        stdout);
}

static int validar_conta(const char *conta)
{
    if (!conta || !*conta) return 0;
    size_t len = strlen(conta);
    if (len > 128) return 0;
    if (conta[0] == '-' || conta[len - 1] == '-') return 0;
    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)conta[i];
        if (!(isalnum(c) || c == '-')) return 0;
    }
    return 1;
}

static int validar_nome_base(const char *base)
{
    if (!base || !*base) return 0;
    size_t len = strlen(base);
    if (len > 128) return 0;
    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)base[i];
        if (!(isalnum(c) || c == '_' || c == '-')) return 0;
    }
    return 1;
}

static int copiar_campo(char *dest, size_t dest_tam, const char *ini, const char *fim)
{
    size_t len = (size_t)(fim - ini);
    if (len == 0 || len + 1 > dest_tam) return -1;
    memcpy(dest, ini, len);
    dest[len] = '\0';
    return 0;
}

static int parse_base_valor(const char *valor, char *base, size_t base_tam,
                            char *usuario, size_t usuario_tam,
                            char *senha, size_t senha_tam)
{
    if (!valor) return -1;

    const char *p = valor;
    while (*p && isspace((unsigned char)*p)) p++;
    const char *ini_base = p;
    while (*p && !isspace((unsigned char)*p)) p++;
    const char *fim_base = p;

    while (*p && isspace((unsigned char)*p)) p++;
    const char *ini_usuario = p;
    while (*p && !isspace((unsigned char)*p)) p++;
    const char *fim_usuario = p;

    while (*p && isspace((unsigned char)*p)) p++;
    const char *ini_senha = p;
    const char *fim_senha = valor + strlen(valor);
    while (fim_senha > ini_senha && isspace((unsigned char)*(fim_senha - 1))) fim_senha--;

    if (copiar_campo(base, base_tam, ini_base, fim_base) != 0) return -1;
    if (copiar_campo(usuario, usuario_tam, ini_usuario, fim_usuario) != 0) return -1;
    if (copiar_campo(senha, senha_tam, ini_senha, fim_senha) != 0) return -1;
    return 0;
}

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

static int juntar_path(char *out, size_t tam, const char *base, const char *nome)
{
    size_t lb = strlen(base), ln = strlen(nome);
    if (lb + 1 + ln + 1 > tam) return -1;
    memcpy(out, base, lb);
    out[lb] = '/';
    memcpy(out + lb + 1, nome, ln + 1);
    return 0;
}

static int juntar_sufixo(char *out, size_t tam, const char *base, const char *sufixo)
{
    size_t lb = strlen(base), ls = strlen(sufixo);
    if (lb + ls + 1 > tam) return -1;
    memcpy(out, base, lb);
    memcpy(out + lb, sufixo, ls + 1);
    return 0;
}

static int existe_path(const char *path)
{
    struct stat st;
    return stat(path, &st) == 0;
}

static int existe_git_na_raiz(const char *cwd)
{
    char git[PATH_MAX];
    if (juntar_path(git, sizeof(git), cwd, ".git") != 0) return 0;
    return existe_path(git);
}

static int run_cmd(const char *const argv[], const char *cwd, const char *git_ssh, int silencioso)
{
    pid_t pid = fork();
    if (pid < 0) return -1;

    if (pid == 0) {
        if (cwd && chdir(cwd) != 0) _exit(127);
        if (git_ssh) setenv("GIT_SSH_COMMAND", git_ssh, 1);
        if (silencioso) {
            int fd = open("/dev/null", O_RDWR);
            if (fd >= 0) {
                dup2(fd, STDOUT_FILENO);
                dup2(fd, STDERR_FILENO);
                if (fd > STDERR_FILENO) close(fd);
            }
        }
        execvp(argv[0], const_cast<char *const *>(argv));
        _exit(127);
    }

    int status = 0;
    if (waitpid(pid, &status, 0) < 0) return -1;
    if (WIFEXITED(status)) return WEXITSTATUS(status);
    return -1;
}

static char *ler_arquivo(const char *caminho)
{
    FILE *fp = fopen(caminho, "rb");
    if (!fp) return NULL;
    if (fseek(fp, 0, SEEK_END) != 0) { fclose(fp); return NULL; }
    long tam = ftell(fp);
    if (tam < 0 || tam > 1024 * 1024) { fclose(fp); return NULL; }
    rewind(fp);

    char *buf = static_cast<char *>(malloc((size_t)tam + 1));
    if (!buf) { fclose(fp); return NULL; }
    size_t lido = fread(buf, 1, (size_t)tam, fp);
    fclose(fp);
    buf[lido] = '\0';
    return buf;
}

static int montar_caminhos_chave(const char *conta, char *priv, size_t priv_tam,
                                 char *pub, size_t pub_tam)
{
    char arquivo[256];
    snprintf(arquivo, sizeof(arquivo), "github-%s-ed25519", conta);

    if (garantir_dir(DIR_SECRETOS, 02770) != 0) return -1;

    if (juntar_path(priv, priv_tam, DIR_SECRETOS, arquivo) != 0) return -1;
    if (juntar_sufixo(pub, pub_tam, priv, ".pub") != 0) return -1;
    return 0;
}

static int emitir_pub_de_priv(const char *priv, const char *pub)
{
    pid_t pid = fork();
    if (pid < 0) return -1;
    if (pid == 0) {
        int out = open(pub, O_WRONLY | O_CREAT | O_TRUNC, 0644);
        int err = open("/dev/null", O_WRONLY);
        if (out < 0) _exit(127);
        dup2(out, STDOUT_FILENO);
        if (err >= 0) dup2(err, STDERR_FILENO);
        if (out > STDERR_FILENO) close(out);
        if (err > STDERR_FILENO) close(err);
        const char *args[] = { "ssh-keygen", "-y", "-f", priv, NULL };
        execvp(args[0], const_cast<char *const *>(args));
        _exit(127);
    }
    int status = 0;
    if (waitpid(pid, &status, 0) < 0) return -1;
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) return -1;
    chmod(pub, 0644);
    return 0;
}

static int garantir_chave_ssh(const char *conta, char *priv, size_t priv_tam,
                              char *pub, size_t pub_tam)
{
    if (montar_caminhos_chave(conta, priv, priv_tam, pub, pub_tam) != 0) return -1;
    if (existe_path(priv) && existe_path(pub)) return 0;
    if (existe_path(priv) && !existe_path(pub)) return emitir_pub_de_priv(priv, pub);

    char comentario[256];
    snprintf(comentario, sizeof(comentario), "%s@github", conta);

    const char *args[] = {
        "ssh-keygen", "-t", "ed25519", "-C", comentario,
        "-f", priv, "-N", "", "-q", NULL
    };

    if (run_cmd(args, NULL, NULL, 1) != 0) return -1;
    chmod(priv, 0600);
    chmod(pub, 0644);
    return existe_path(priv) && existe_path(pub) ? 0 : -1;
}

static int exportar_base_mysql(const char *cwd, const char *base, const char *usuario,
                               const char *senha, char *saida, size_t saida_tam)
{
    char dir_sql[PATH_MAX];
    if (juntar_path(dir_sql, sizeof(dir_sql), cwd, "sql") != 0) return -1;
    if (garantir_dir(dir_sql, 0755) != 0) return -1;

    char arquivo[256];
    snprintf(arquivo, sizeof(arquivo), "%s.sql", base);
    if (juntar_path(saida, saida_tam, dir_sql, arquivo) != 0) return -1;

    char temp[PATH_MAX];
    if (juntar_sufixo(temp, sizeof(temp), saida, ".tmp") != 0) return -1;

    int fd = open(temp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) return -1;

    pid_t pid = fork();
    if (pid < 0) {
        close(fd);
        unlink(temp);
        return -1;
    }

    if (pid == 0) {
        if (cwd && chdir(cwd) != 0) _exit(127);
        setenv("MYSQL_PWD", senha ? senha : "", 1);
        dup2(fd, STDOUT_FILENO);
        if (fd > STDERR_FILENO) close(fd);
        const char *args[] = {
            "mysqldump",
            "--single-transaction",
            "--routines",
            "--triggers",
            "--events",
            "--default-character-set=utf8mb4",
            "-h", "127.0.0.1",
            "-u", usuario,
            "--databases", base,
            NULL
        };
        execvp(args[0], const_cast<char *const *>(args));
        _exit(127);
    }

    close(fd);
    int status = 0;
    if (waitpid(pid, &status, 0) < 0) {
        unlink(temp);
        return -1;
    }
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
        unlink(temp);
        return -1;
    }
    if (rename(temp, saida) != 0) {
        unlink(temp);
        return -1;
    }
    chmod(saida, 0644);
    return 0;
}

static const char *basename_cwd(const char *cwd)
{
    const char *base = strrchr(cwd, '/');
    if (!base) return cwd;
    if (*(base + 1) == '\0') return "repositorio";
    return base + 1;
}

int main(int argc, char *argv[])
{ 
    if (argc < 2) {
        uso();
        return 1;
    }

    const char *conta = NULL;
    int emitir_ssh = 0;
    const char *mensagem_commit = "Atualização";
    const char *base_nome = NULL;
    const char *base_usuario = NULL;
    const char *base_senha = NULL;
    char base_nome_buf[129];
    char base_usuario_buf[256];
    char base_senha_buf[1024];

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--ajuda") == 0 || strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
            uso();
            return 0;
        }
        if (strncmp(argv[i], "--push-github=", 14) == 0) {
            conta = argv[i] + 14;
        } else if (strcmp(argv[i], "--push-github") == 0) {
            /* token puro removido pelo roteador kk; ignorado por compatibilidade */
        } else if (argv[i][0] != '-' && !conta) {
            conta = argv[i];
        } else if (strcmp(argv[i], "--emitir-ssh") == 0) {
            emitir_ssh = 1;
        } else if (strcmp(argv[i], "--commit") == 0) {
            if (i + 1 >= argc || argv[i + 1][0] == '\0') {
                fprintf(stderr, "push-github: mensagem de commit vazia.\n");
                return 1;
            }
            mensagem_commit = argv[++i];
        } else if (strcmp(argv[i], "--base") == 0) {
            if (i + 1 >= argc || parse_base_valor(argv[i + 1], base_nome_buf, sizeof(base_nome_buf), base_usuario_buf, sizeof(base_usuario_buf), base_senha_buf, sizeof(base_senha_buf)) != 0) {
                fprintf(stderr, "push-github: use --base '<base> <usuario> <senha>'.\n");
                return 1;
            }
            i++;
            base_nome = base_nome_buf;
            base_usuario = base_usuario_buf;
            base_senha = base_senha_buf;
        } else if (strncmp(argv[i], "--base=", 7) == 0) {
            if (parse_base_valor(argv[i] + 7, base_nome_buf, sizeof(base_nome_buf), base_usuario_buf, sizeof(base_usuario_buf), base_senha_buf, sizeof(base_senha_buf)) != 0) {
                fprintf(stderr, "push-github: use --base '<base> <usuario> <senha>'.\n");
                return 1;
            }
            base_nome = base_nome_buf;
            base_usuario = base_usuario_buf;
            base_senha = base_senha_buf;
        } else if (strncmp(argv[i], "--commit=", 9) == 0) {
            mensagem_commit = argv[i] + 9;
            if (mensagem_commit[0] == '\0') {
                fprintf(stderr, "push-github: mensagem de commit vazia.\n");
                return 1;
            }
        } else {
            uso();
            return 1;
        }
    }

    if (!validar_conta(conta)) {
        fprintf(stderr, "push-github: conta inválida.\n");
        return 1;
    }

    if (base_nome || base_usuario || base_senha) {
        if (!validar_nome_base(base_nome) || !base_usuario || !*base_usuario || !base_senha) {
            fprintf(stderr, "push-github: dados da base inválidos; use --base '<base> <usuario> <senha>'.\n");
            return 1;
        }
    }

    char chave_priv[PATH_MAX];
    char chave_pub[PATH_MAX];

    if (emitir_ssh) {
        if (garantir_chave_ssh(conta, chave_priv, sizeof(chave_priv), chave_pub, sizeof(chave_pub)) != 0) {
            return 1;
        }
        char *pub = ler_arquivo(chave_pub);
        if (!pub) return 1;
        fputs(pub, stdout);
        free(pub);
        return 0;
    }

    if (montar_caminhos_chave(conta, chave_priv, sizeof(chave_priv), chave_pub, sizeof(chave_pub)) != 0) {
        fprintf(stderr, "push-github: não foi possível acessar secretos.\n");
        return 1;
    }
    if (!existe_path(chave_priv)) {
        fprintf(stderr,
                "push-github: chave SSH ausente para a conta '%s' em %s; execute com --emitir-ssh e cadastre a chave no GitHub.\n",
                conta, chave_priv);
        return 1;
    }

    char cwd[PATH_MAX];
    if (!getcwd(cwd, sizeof(cwd))) {
        fprintf(stderr, "push-github: não foi possível detectar a pasta atual.\n");
        return 1;
    }

    if (!existe_git_na_raiz(cwd)) {
        const char *args_init[] = { "git", "init", NULL };
        if (run_cmd(args_init, cwd, NULL, 0) != 0) return 1;
    }

    if (base_nome) {
        char dump_sql[PATH_MAX];
        printf("push-github: exportando base '%s' para sql/%s.sql\n", base_nome, base_nome);
        if (exportar_base_mysql(cwd, base_nome, base_usuario, base_senha, dump_sql, sizeof(dump_sql)) != 0) {
            fprintf(stderr, "push-github: falha ao exportar a base '%s' com mysqldump.\n", base_nome);
            return 1;
        }
    }

    const char *args_add[] = { "git", "add", "-A", "--", ".", NULL };
    if (run_cmd(args_add, cwd, NULL, 0) != 0) return 1;

    const char *args_head[] = { "git", "rev-parse", "--verify", "HEAD", NULL };
    int tem_head = (run_cmd(args_head, cwd, NULL, 1) == 0);

    const char *args_diff[] = { "git", "diff", "--cached", "--quiet", "--exit-code", NULL };
    int diff = run_cmd(args_diff, cwd, NULL, 1);
    int tem_staged = (diff == 1);

    char email[256];
    snprintf(email, sizeof(email), "%s@users.noreply.github.com", conta);
    const char *args_name[] = { "git", "config", "user.name", conta, NULL };
    const char *args_email[] = { "git", "config", "user.email", email, NULL };
    if (run_cmd(args_name, cwd, NULL, 1) != 0) return 1;
    if (run_cmd(args_email, cwd, NULL, 1) != 0) return 1;

    if (tem_staged) {
        const char *args_commit[] = { "git", "commit", "-m", mensagem_commit, NULL };
        if (run_cmd(args_commit, cwd, NULL, 0) != 0) return 1;
    } else if (!tem_head) {
        const char *args_commit_empty[] = { "git", "commit", "--allow-empty", "-m", mensagem_commit, NULL };
        if (run_cmd(args_commit_empty, cwd, NULL, 0) != 0) return 1;
    }

    const char *args_branch[] = { "git", "branch", "-M", "main", NULL };
    if (run_cmd(args_branch, cwd, NULL, 0) != 0) return 1;

    const char *repo = basename_cwd(cwd);
    char remoto[PATH_MAX + 256];
    snprintf(remoto, sizeof(remoto), "git@github.com:%s/%s.git", conta, repo);

    const char *args_get_origin[] = { "git", "remote", "get-url", "origin", NULL };
    if (run_cmd(args_get_origin, cwd, NULL, 1) == 0) {
        const char *args_set_origin[] = { "git", "remote", "set-url", "origin", remoto, NULL };
        if (run_cmd(args_set_origin, cwd, NULL, 0) != 0) return 1;
    } else {
        const char *args_add_origin[] = { "git", "remote", "add", "origin", remoto, NULL };
        if (run_cmd(args_add_origin, cwd, NULL, 0) != 0) return 1;
    }

    char git_ssh[PATH_MAX + 128];
    snprintf(git_ssh, sizeof(git_ssh),
             "ssh -i '%s' -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new",
             chave_priv);

    const char *args_push[] = { "git", "push", "-u", "origin", "main", NULL };
    if (run_cmd(args_push, cwd, git_ssh, 0) != 0) return 1;

    return 0;
}
