/*
 * criar-conta-llm - Cria ou atualiza preset de conta LLM baseado em contas-llm/llm.json
 * Uso:
 *   pp --criar-conta-llm=<KEY_OU_ARQUIVO> [--modelo=<NOME>]
 *   pp --criar-conta-llm=auth|oauth
 *
 * Comportamento:
 *   - Usa contas-llm/llm.json como base
 *   - Força tipo=key
 *   - Gera conexão canônica: <modelo_sem_hifen>-<provedor>-<conta>-key
 *   - Salva preset em contas-llm/<conexao>.json
 *   - Salva chave real em secretos/<conexao>.txt
 *   - Atualiza contas-llm/llm.json para apontar para a conexão
 *   - Se a conta já existir, atualiza a configuração (não dá erro)
 *   - Se <KEY_OU_ARQUIVO> for nome de arquivo em secretos/, usa o conteúdo do arquivo como chave
 *   - --modelo=<NOME> sobrescreve o modelo no preset
 */

#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/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <openssl/sha.h>
#include "../restricao.h"

#define CODEX_CLIENT_ID "app_EMoamEEZ73f0CkXaXp7hrann"
#define CODEX_REDIRECT_URI "http://localhost:1455/auth/callback"
#define CODEX_TOKEN_URL "https://auth.openai.com/oauth/token"

static char *ler_arquivo(const char *caminho)
{
    FILE *fp = fopen(caminho, "rb");
    if (!fp) return NULL;
    fseek(fp, 0, SEEK_END);
    long tam = ftell(fp);
    if (tam < 0 || tam > 20 * 1024 * 1024) { fclose(fp); return NULL; }
    fseek(fp, 0, SEEK_SET);
    char *buf = 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 escrever_arquivo(const char *caminho, const char *conteudo, mode_t modo)
{
    FILE *fp = fopen(caminho, "wb");
    if (!fp) return -1;
    size_t len = strlen(conteudo);
    size_t escrito = fwrite(conteudo, 1, len, fp);
    if (len == 0 || conteudo[len - 1] != '\n') fputc('\n', fp);
    fclose(fp);
    chmod(caminho, modo);
    return escrito == len ? 0 : -1;
}

static char *json_escape(const char *s)
{
    size_t len = strlen(s), cap = len * 2 + 8;
    char *out = malloc(cap);
    if (!out) return NULL;
    size_t j = 0;
    for (size_t i = 0; i < len; i++) {
        if (j + 8 >= cap) { cap *= 2; out = realloc(out, cap); if (!out) return NULL; }
        switch (s[i]) {
            case '"': out[j++] = '\\'; out[j++] = '"'; break;
            case '\\': out[j++] = '\\'; out[j++] = '\\'; break;
            case '\n': out[j++] = '\\'; out[j++] = 'n'; break;
            case '\r': out[j++] = '\\'; out[j++] = 'r'; break;
            case '\t': out[j++] = '\\'; out[j++] = 't'; break;
            default: out[j++] = s[i]; break;
        }
    }
    out[j] = '\0';
    return out;
}

static char *json_get_string(const char *json, const char *chave)
{
    char padrao[256];
    snprintf(padrao, sizeof(padrao), "\"%s\"", chave);
    const char *p = strstr(json, padrao);
    if (!p) return NULL;
    p = strchr(p + strlen(padrao), ':');
    if (!p) return NULL;
    p++;
    while (*p && isspace((unsigned char)*p)) p++;
    if (*p != '"') return NULL;
    p++;

    size_t cap = 128, len = 0;
    char *out = malloc(cap);
    if (!out) return NULL;
    while (*p && *p != '"') {
        char c = *p;
        if (c == '\\' && p[1]) {
            p++;
            c = *p;
            if (c == 'n') c = '\n';
            else if (c == 'r') c = '\r';
            else if (c == 't') c = '\t';
        }
        if (len + 2 >= cap) { cap *= 2; out = realloc(out, cap); if (!out) return NULL; }
        out[len++] = c;
        p++;
    }
    out[len] = '\0';
    return out;
}

static char *substituir_intervalo(const char *src, const char *ini, const char *fim, const char *novo)
{
    size_t a = (size_t)(ini - src);
    size_t b = strlen(fim);
    size_t n = strlen(novo);
    char *out = malloc(a + n + b + 1);
    if (!out) return NULL;
    memcpy(out, src, a);
    memcpy(out + a, novo, n);
    memcpy(out + a + n, fim, b + 1);
    return out;
}

static char *json_set_string_first(const char *json, const char *chave, const char *valor)
{
    char padrao[256];
    snprintf(padrao, sizeof(padrao), "\"%s\"", chave);
    const char *p = strstr(json, padrao);
    if (!p) return strdup(json);
    p = strchr(p + strlen(padrao), ':');
    if (!p) return strdup(json);
    p++;
    while (*p && isspace((unsigned char)*p)) p++;
    if (*p != '"') return strdup(json);
    const char *ini = p + 1;
    const char *q = ini;
    while (*q && *q != '"') {
        if (*q == '\\' && q[1]) q++;
        q++;
    }
    if (*q != '"') return strdup(json);

    char *esc = json_escape(valor);
    if (!esc) return NULL;
    char *out = substituir_intervalo(json, ini, q, esc);
    free(esc);
    return out;
}

static const char *achar_fim_objeto_json(const char *abre)
{
    int nivel = 0, em_string = 0, escape = 0;
    for (const char *p = abre; *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 == '{') nivel++;
        else if (*p == '}') {
            nivel--;
            if (nivel == 0) return p + 1;
        }
    }
    return NULL;
}

static char *json_set_autenticacao_bearer(const char *json, const char *conexao)
{
    char *esc = json_escape(conexao);
    if (!esc) return NULL;
    size_t nobj = strlen(esc) + 96;
    char *obj = malloc(nobj);
    if (!obj) { free(esc); return NULL; }
    snprintf(obj, nobj, "{\n        \"tipo\": \"bearer\",\n        \"apiKey\": \"%s\"\n    }", esc);
    free(esc);

    const char *key = strstr(json, "\"autenticacao\"");
    if (key) {
        const char *colon = strchr(key, ':');
        if (colon) {
            const char *abre = strchr(colon, '{');
            if (abre) {
                const char *fim = achar_fim_objeto_json(abre);
                if (fim) {
                    char *out = substituir_intervalo(json, abre, fim, obj);
                    free(obj);
                    return out;
                }
            }
        }
    }

    const char *fim_doc = strrchr(json, '}');
    if (!fim_doc) { free(obj); return strdup(json); }
    size_t add_len = strlen(obj) + 32;
    char *add = malloc(add_len);
    if (!add) { free(obj); return NULL; }
    snprintf(add, add_len, ",\n    \"autenticacao\": %s\n", obj);
    free(obj);
    char *out = substituir_intervalo(json, fim_doc, fim_doc, add);
    free(add);
    return out;
}

static char *modelo_slug(const char *modelo)
{
    size_t len = strlen(modelo);
    char *out = malloc(len + 1);
    if (!out) return NULL;
    size_t j = 0;
    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)modelo[i];
        if (c == '-' || isspace(c)) continue;
        out[j++] = (char)tolower(c);
    }
    out[j] = '\0';
    return out;
}

static char *conexao_canonica(const char *modelo, const char *provedor, const char *conta)
{
    char *slug = modelo_slug(modelo);
    if (!slug) return NULL;
    size_t len = strlen(slug) + strlen(provedor) + strlen(conta) + 8;
    char *out = malloc(len);
    if (!out) { free(slug); return NULL; }
    snprintf(out, len, "%s-%s-%s-key", slug, provedor, conta);
    free(slug);
    return out;
}

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 raiz_projeto(char *raiz, size_t tam)
{
    /* Cada comando trata o diretório de execução como raiz do sistema. */
    return getcwd(raiz, tam) ? 0 : -1;
}

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

static void resolver_subdir_sistema(const char *raiz, const char *nome, char *out, size_t tam)
{
    char direto[PATH_MAX * 2];

    /* Passo 1: local no sistema atual */
    snprintf(direto, sizeof(direto), "%s/%s", raiz, nome);
    if (eh_diretorio(direto)) {
        snprintf(out, tam, "%s", direto);
        return;
    }
    if (strcmp(nome, "contas-llm") == 0) {
        snprintf(out, tam, "%s", direto);
        return;
    }

    /* Passo 2: fallback para core/biblioteca-<nome>/ */
    char raiz_sisc[PATH_MAX];
    snprintf(raiz_sisc, sizeof(raiz_sisc), "%s", raiz);
    char *slash = strrchr(raiz_sisc, '/');
    if (slash && slash != raiz_sisc) {
        *slash = '\0';
        char biblioteca[PATH_MAX * 2];
        snprintf(biblioteca, sizeof(biblioteca), "%s/core/biblioteca-%s", raiz_sisc, nome);
        if (eh_diretorio(biblioteca)) {
            snprintf(out, tam, "%s", biblioteca);
            return;
        }
    }

    snprintf(out, tam, "%s", direto);
}

/* Lê a chave: se existir arquivo em secretos/ com o nome informado (com ou sem .txt),
 * usa esse arquivo existente (aponta para ele na configuração da conta).
 * Caso contrário, trata o argumento como a própria chave e salva em novo arquivo. */
static char *resolver_chave(const char *raiz, const char *arg, const char *conexao)
{
    (void)conexao;
    char dir_secretos[PATH_MAX * 2];
    resolver_subdir_sistema(raiz, "secretos", dir_secretos, sizeof(dir_secretos));
    struct stat st;
    char caminho[PATH_MAX * 2];
    
    /* Tenta sem .txt */
    snprintf(caminho, sizeof(caminho), "%s/%s", dir_secretos, arg);
    if (stat(caminho, &st) == 0 && S_ISREG(st.st_mode)) {
        char *ref = strdup(arg);
        if (ref) { char *ext = strstr(ref, ".txt"); if (ext) *ext = '\0'; }
        return ref;
    }
    /* Tenta com .txt */
    size_t arglen = strlen(arg);
    if (arglen < PATH_MAX * 2 - 5) {
        char arg_txt[PATH_MAX * 2];
        snprintf(arg_txt, sizeof(arg_txt), "%s.txt", arg);
        snprintf(caminho, sizeof(caminho), "%s/%s", dir_secretos, arg_txt);
        if (stat(caminho, &st) == 0 && S_ISREG(st.st_mode)) {
            char *ref = strdup(arg);
            if (ref) { char *ext = strstr(ref, ".txt"); if (ext) *ext = '\0'; }
            return ref;
        }
    }
    /* Não é arquivo existente: trata o argumento como a chave e depois
     * salvamos em secretos/<conexao>.txt */
    return strdup(arg);
}

static int conta_existe_na_biblioteca(const char *raiz, const char *conexao)
{
    char raiz_sisc[PATH_MAX], path[PATH_MAX * 2];
    snprintf(raiz_sisc, sizeof(raiz_sisc), "%s", raiz);
    char *slash = strrchr(raiz_sisc, '/');
    if (!slash || slash == raiz_sisc) return 0;
    *slash = '\0';
    snprintf(path, sizeof(path), "%s/core/biblioteca-contas-llm/%s.json", raiz_sisc, conexao);
    return access(path, F_OK) == 0;
}

static char *base64url(const unsigned char *dados, size_t len)
{
    static const char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
    size_t out_len = ((len + 2) / 3) * 4;
    char *out = malloc(out_len + 1);
    if (!out) return NULL;
    size_t j = 0;
    for (size_t i = 0; i < len; i += 3) {
        unsigned int v = dados[i] << 16;
        int rem = (int)(len - i);
        if (rem > 1) v |= dados[i + 1] << 8;
        if (rem > 2) v |= dados[i + 2];
        out[j++] = tbl[(v >> 18) & 63];
        out[j++] = tbl[(v >> 12) & 63];
        if (rem > 1) out[j++] = tbl[(v >> 6) & 63];
        if (rem > 2) out[j++] = tbl[v & 63];
    }
    out[j] = '\0';
    return out;
}

static int random_bytes(unsigned char *buf, size_t len)
{
    FILE *fp = fopen("/dev/urandom", "rb");
    if (!fp) return -1;
    size_t n = fread(buf, 1, len, fp);
    fclose(fp);
    return n == len ? 0 : -1;
}

static char *pkce_verifier(void)
{
    unsigned char rnd[32];
    if (random_bytes(rnd, sizeof(rnd)) != 0) return NULL;
    return base64url(rnd, sizeof(rnd));
}

static char *pkce_challenge(const char *verifier)
{
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256((const unsigned char *)verifier, strlen(verifier), hash);
    return base64url(hash, sizeof(hash));
}

static char *state_hex(void)
{
    unsigned char rnd[16];
    if (random_bytes(rnd, sizeof(rnd)) != 0) return NULL;
    char *out = malloc(33);
    if (!out) return NULL;
    for (int i = 0; i < 16; i++) sprintf(out + i * 2, "%02x", rnd[i]);
    out[32] = '\0';
    return out;
}

static char *url_encode(const char *s)
{
    size_t len = strlen(s);
    char *out = malloc(len * 3 + 1);
    if (!out) return NULL;
    size_t j = 0;
    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)s[i];
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') out[j++] = (char)c;
        else { sprintf(out + j, "%%%02X", c); j += 3; }
    }
    out[j] = '\0';
    return out;
}

static char *montar_url_auth(const char *challenge, const char *state)
{
    char *ch = url_encode(challenge), *st = url_encode(state), *redir = url_encode(CODEX_REDIRECT_URI);
    if (!ch || !st || !redir) { free(ch); free(st); free(redir); return NULL; }
    const char *fmt = "https://auth.openai.com/oauth/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=openid%%20profile%%20email%%20offline_access&code_challenge=%s&code_challenge_method=S256&state=%s&id_token_add_organizations=true&codex_cli_simplified_flow=true&originator=sisc";
    size_t n = strlen(fmt) + strlen(CODEX_CLIENT_ID) + strlen(redir) + strlen(ch) + strlen(st) + 16;
    char *url = malloc(n);
    if (url) snprintf(url, n, fmt, CODEX_CLIENT_ID, redir, ch, st);
    free(ch); free(st); free(redir);
    return url;
}

static char *extrair_param(const char *texto, const char *nome)
{
    size_t nl = strlen(nome);
    const char *p = strstr(texto, nome);
    while (p) {
        if ((p == texto || p[-1] == '?' || p[-1] == '&' || p[-1] == ' ' || p[-1] == '/') && p[nl] == '=') {
            p += nl + 1;
            const char *q = p;
            while (*q && *q != '&' && *q != ' ' && *q != '#' && *q != '\r' && *q != '\n') q++;
            size_t len = (size_t)(q - p);
            char *out = malloc(len + 1);
            if (!out) return NULL;
            memcpy(out, p, len);
            out[len] = '\0';
            return out;
        }
        p = strstr(p + 1, nome);
    }
    return NULL;
}

static char *parse_code_manual(const char *entrada, const char *state)
{
    char *st = extrair_param(entrada, "state");
    if (st && strcmp(st, state) != 0) {
        free(st);
        fprintf(stderr, "criar-conta-llm: state divergente.\n");
        return NULL;
    }
    free(st);
    char *code = extrair_param(entrada, "code");
    if (code) return code;
    char *limpo = strdup(entrada);
    if (!limpo) return NULL;
    char *p = limpo;
    while (*p && isspace((unsigned char)*p)) p++;
    char *fim = p + strlen(p);
    while (fim > p && isspace((unsigned char)fim[-1])) *--fim = '\0';
    if (*p == '\0') { free(limpo); return NULL; }
    char *out = strdup(p);
    free(limpo);
    return out;
}

static char *aguardar_callback(const char *state, int timeout_seg)
{
    int srv = socket(AF_INET, SOCK_STREAM, 0);
    if (srv < 0) return NULL;
    int opt = 1;
    setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1455);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (bind(srv, (struct sockaddr *)&addr, sizeof(addr)) != 0 || listen(srv, 1) != 0) {
        close(srv);
        return NULL;
    }

    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(srv, &fds);
    struct timeval tv;
    tv.tv_sec = timeout_seg;
    tv.tv_usec = 0;
    int sel = select(srv + 1, &fds, NULL, NULL, &tv);
    if (sel <= 0) { close(srv); return NULL; }

    int cli = accept(srv, NULL, NULL);
    if (cli < 0) { close(srv); return NULL; }
    char req[8192];
    ssize_t n = read(cli, req, sizeof(req) - 1);
    if (n < 0) n = 0;
    req[n] = '\0';

    char *code = NULL;
    char *st = extrair_param(req, "state");
    if (st && strcmp(st, state) == 0) code = extrair_param(req, "code");
    int ok = code != NULL;
    const char *body_ok = "<h1>Login concluido</h1><p>Voce pode fechar esta janela.</p>";
    const char *body_err = "<h1>Login nao concluido</h1><p>Callback invalido.</p>";
    const char *body = ok ? body_ok : body_err;
    char resp[1024];
    snprintf(resp, sizeof(resp), "HTTP/1.1 %s\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n%s", ok ? "200 OK" : "400 Bad Request", body);
    write(cli, resp, strlen(resp));
    close(cli);
    close(srv);
    free(st);
    return code;
}

static char *criar_temp_payload(const char *conteudo)
{
    char tmpl[] = "/tmp/sisc_oauth_payload_XXXXXX";
    int fd = mkstemp(tmpl);
    if (fd < 0) return NULL;
    write(fd, conteudo, strlen(conteudo));
    close(fd);
    return strdup(tmpl);
}

static char *trocar_codigo_token(const char *code, const char *verifier)
{
    char *code_e = url_encode(code), *ver_e = url_encode(verifier), *redir_e = url_encode(CODEX_REDIRECT_URI), *client_e = url_encode(CODEX_CLIENT_ID);
    if (!code_e || !ver_e || !redir_e || !client_e) { free(code_e); free(ver_e); free(redir_e); free(client_e); return NULL; }
    size_t body_len = strlen(code_e) + strlen(ver_e) + strlen(redir_e) + strlen(client_e) + 160;
    char *body = malloc(body_len);
    if (!body) { free(code_e); free(ver_e); free(redir_e); free(client_e); return NULL; }
    snprintf(body, body_len, "grant_type=authorization_code&client_id=%s&code=%s&code_verifier=%s&redirect_uri=%s", client_e, code_e, ver_e, redir_e);
    free(code_e); free(ver_e); free(redir_e); free(client_e);

    char *payload_path = criar_temp_payload(body);
    free(body);
    if (!payload_path) return NULL;

    char resp_tmpl[] = "/tmp/sisc_oauth_resp_XXXXXX";
    int fd = mkstemp(resp_tmpl);
    if (fd < 0) { unlink(payload_path); free(payload_path); return NULL; }
    close(fd);

    char cmd[4096];
    snprintf(cmd, sizeof(cmd), "curl -s -w '\\n%%{http_code}' --max-time 60 -H 'Content-Type: application/x-www-form-urlencoded' --data @%s -o %s '%s'", payload_path, resp_tmpl, CODEX_TOKEN_URL);
    FILE *fp = popen(cmd, "r");
    char code_http[16] = "";
    if (fp) {
        char line[64];
        while (fgets(line, sizeof(line), fp)) {
            size_t l = strlen(line);
            if (l && line[l - 1] == '\n') line[l - 1] = '\0';
            if (isdigit((unsigned char)line[0])) snprintf(code_http, sizeof(code_http), "%s", line);
        }
        pclose(fp);
    }
    char *resposta = ler_arquivo(resp_tmpl);
    unlink(payload_path); unlink(resp_tmpl); free(payload_path);
    if (!resposta) return NULL;
    if (atoi(code_http) >= 400) {
        fprintf(stderr, "criar-conta-llm: falha HTTP %s na troca de token: %.500s\n", code_http, resposta);
        free(resposta);
        return NULL;
    }
    return resposta;
}

static unsigned char b64url_val(char c)
{
    if (c >= 'A' && c <= 'Z') return (unsigned char)(c - 'A');
    if (c >= 'a' && c <= 'z') return (unsigned char)(c - 'a' + 26);
    if (c >= '0' && c <= '9') return (unsigned char)(c - '0' + 52);
    if (c == '-') return 62;
    if (c == '_') return 63;
    return 255;
}

static char *base64url_decode_str(const char *s)
{
    size_t len = strlen(s), cap = len * 3 / 4 + 4;
    unsigned char *out = malloc(cap);
    if (!out) return NULL;
    unsigned int buf = 0;
    int bits = 0;
    size_t j = 0;
    for (size_t i = 0; i < len; i++) {
        if (s[i] == '=') break;
        unsigned char v = b64url_val(s[i]);
        if (v == 255) continue;
        buf = (buf << 6) | v;
        bits += 6;
        if (bits >= 8) {
            bits -= 8;
            out[j++] = (unsigned char)((buf >> bits) & 0xff);
        }
    }
    out[j] = '\0';
    return (char *)out;
}

static char *account_id_from_access(const char *access)
{
    const char *p1 = strchr(access, '.');
    if (!p1) return NULL;
    const char *p2 = strchr(p1 + 1, '.');
    if (!p2) return NULL;
    size_t len = (size_t)(p2 - p1 - 1);
    char *payload = malloc(len + 1);
    if (!payload) return NULL;
    memcpy(payload, p1 + 1, len);
    payload[len] = '\0';
    char *json = base64url_decode_str(payload);
    free(payload);
    if (!json) return NULL;
    char *id = json_get_string(json, "chatgpt_account_id");
    free(json);
    return id;
}

static char *json_set_url_default(const char *json)
{
    char *url = json_get_string(json, "url");
    if (url && url[0]) { free(url); return strdup(json); }
    free(url);
    return json_set_string_first(json, "url", "https://chatgpt.com/backend-api");
}

static char *json_set_autenticacao_oauth(const char *json, const char *conexao)
{
    char *esc = json_escape(conexao);
    if (!esc) return NULL;
    size_t nobj = strlen(esc) + 160;
    char *obj = malloc(nobj);
    if (!obj) { free(esc); return NULL; }
    snprintf(obj, nobj, "{\n        \"tipo\": \"oauth\",\n        \"arquivo\": \"%s\",\n        \"clientId\": \"%s\"\n    }", esc, CODEX_CLIENT_ID);
    free(esc);
    const char *key = strstr(json, "\"autenticacao\"");
    if (key) {
        const char *colon = strchr(key, ':');
        const char *abre = colon ? strchr(colon, '{') : NULL;
        const char *fim = abre ? achar_fim_objeto_json(abre) : NULL;
        if (fim) { char *out = substituir_intervalo(json, abre, fim, obj); free(obj); return out; }
    }
    const char *fim_doc = strrchr(json, '}');
    if (!fim_doc) { free(obj); return strdup(json); }
    size_t add_len = strlen(obj) + 32;
    char *add = malloc(add_len);
    if (!add) { free(obj); return NULL; }
    snprintf(add, add_len, ",\n    \"autenticacao\": %s\n", obj);
    free(obj);
    char *out = substituir_intervalo(json, fim_doc, fim_doc, add);
    free(add);
    return out;
}

static int criar_conta_auth(const char *json_base, const char *raiz, const char *modelo, const char *provedor, const char *conta)
{
    if (strcmp(provedor, "codex") != 0 && strcmp(provedor, "openai-codex") != 0) {
        fprintf(stderr, "criar-conta-llm: auth/OAuth suportado apenas para provedor codex/openai-codex. Provedor atual: %s\n", provedor);
        return 1;
    }

    char *slug = modelo_slug(modelo);
    if (!slug) return 1;
    size_t clen = strlen(slug) + strlen(provedor) + strlen(conta) + 10;
    char *conexao = malloc(clen);
    if (!conexao) { free(slug); return 1; }
    snprintf(conexao, clen, "%s-%s-%s-oauth", slug, provedor, conta);
    free(slug);

    /* Credencial OAuth é por provedor+conta, não por modelo.
     * Assim vários presets/modelos podem apontar para o mesmo segredo. */
    size_t cred_nome_len = strlen(provedor) + strlen(conta) + 8;
    char *credencial = malloc(cred_nome_len);
    if (!credencial) { free(conexao); return 1; }
    snprintf(credencial, cred_nome_len, "%s-%s-oauth", provedor, conta);

    char *verifier = pkce_verifier();
    char *challenge = verifier ? pkce_challenge(verifier) : NULL;
    char *state = challenge ? state_hex() : NULL;
    char *url = state ? montar_url_auth(challenge, state) : NULL;
    if (!verifier || !challenge || !state || !url) {
        fprintf(stderr, "criar-conta-llm: erro ao preparar PKCE/OAuth.\n");
        free(conexao); free(credencial); free(verifier); free(challenge); free(state); free(url);
        return 1;
    }

    printf("Abra esta URL no navegador e conclua o login:\n%s\n\n", url);
    printf("Aguardando callback em %s por até 300s...\n", CODEX_REDIRECT_URI);
    fflush(stdout);
    char *code = aguardar_callback(state, 300);
    if (!code) {
        printf("Se o callback automático não funcionou, cole aqui o código ou a URL de redirecionamento: ");
        fflush(stdout);
        char entrada[8192];
        if (!fgets(entrada, sizeof(entrada), stdin)) entrada[0] = '\0';
        code = parse_code_manual(entrada, state);
    }
    if (!code) {
        fprintf(stderr, "criar-conta-llm: código de autorização ausente.\n");
        free(conexao); free(credencial); free(verifier); free(challenge); free(state); free(url);
        return 1;
    }

    char *token_json = trocar_codigo_token(code, verifier);
    free(code); free(verifier); free(challenge); free(state); free(url);
    if (!token_json) { free(conexao); free(credencial); return 1; }

    char *access = json_get_string(token_json, "access_token");
    char *refresh = json_get_string(token_json, "refresh_token");
    char *expires_str = json_get_string(token_json, "expires_in");
    long expires_in = expires_str ? atol(expires_str) : 0;
    free(expires_str);
    if (expires_in <= 0) {
        const char *p = strstr(token_json, "\"expires_in\"");
        if (p && (p = strchr(p, ':'))) expires_in = atol(p + 1);
    }
    if (!access || !refresh || expires_in <= 0) {
        fprintf(stderr, "criar-conta-llm: resposta de token incompleta.\n");
        free(token_json); free(access); free(refresh); free(conexao); free(credencial);
        return 1;
    }
    char *account = account_id_from_access(access);
    if (!account) {
        fprintf(stderr, "criar-conta-llm: não foi possível extrair chatgpt_account_id do access token.\n");
        free(token_json); free(access); free(refresh); free(conexao); free(credencial);
        return 1;
    }

    char dir_secretos[PATH_MAX * 2], dir_contas[PATH_MAX * 2];
    /* Escrita sempre local; o resolver e usado apenas para leitura/consulta */
    snprintf(dir_secretos, sizeof(dir_secretos), "%s/secretos", raiz);
    snprintf(dir_contas, sizeof(dir_contas), "%s/contas-llm", raiz);
    if (garantir_dir(dir_secretos, 0700) != 0 || garantir_dir(dir_contas, 0775) != 0) {
        fprintf(stderr, "criar-conta-llm: não foi possível criar diretórios.\n");
        free(token_json); free(access); free(refresh); free(account); free(conexao); free(credencial);
        return 1;
    }

    {
        char caminho_preset_check[PATH_MAX * 4];
        snprintf(caminho_preset_check, sizeof(caminho_preset_check), "%s/%s.json", dir_contas, conexao);
        struct stat st_check;
        if (stat(caminho_preset_check, &st_check) == 0) {
            fprintf(stderr, "criar-conta-llm: conta ja existe: contas-llm/%s.json\n", conexao);
            free(token_json); free(access); free(refresh); free(account); free(conexao); free(credencial);
            return 1;
        }
        if (conta_existe_na_biblioteca(raiz, conexao)) {
            fprintf(stderr, "criar-conta-llm: conta ja existe na biblioteca: core/biblioteca-contas-llm/%s.json\n", conexao);
            free(token_json); free(access); free(refresh); free(account); free(conexao); free(credencial);
            return 1;
        }
    }

    time_t expira = time(NULL) + expires_in;
    time_t agora = time(NULL);
    char criado[64];
    strftime(criado, sizeof(criado), "%Y-%m-%dT%H:%M:%SZ", gmtime(&agora));
    char *access_e = json_escape(access), *refresh_e = json_escape(refresh), *account_e = json_escape(account), *prov_e = json_escape(provedor);
    size_t cred_len = strlen(access_e) + strlen(refresh_e) + strlen(account_e) + strlen(prov_e) + 256;
    char *cred = malloc(cred_len);
    snprintf(cred, cred_len,
        "{\n    \"type\": \"oauth\",\n    \"provedor\": \"%s\",\n    \"access\": \"%s\",\n    \"refresh\": \"%s\",\n    \"expires\": %ld,\n    \"accountId\": \"%s\",\n    \"criadoEm\": \"%s\"\n}",
        prov_e, access_e, refresh_e, (long)expira, account_e, criado);
    free(access_e); free(refresh_e); free(account_e); free(prov_e);

    char caminho_cred[PATH_MAX * 4];
    snprintf(caminho_cred, sizeof(caminho_cred), "%s/%s.json", dir_secretos, credencial);
    if (!cred || escrever_arquivo(caminho_cred, cred, 0600) != 0) {
        fprintf(stderr, "criar-conta-llm: não foi possível salvar credenciais OAuth.\n");
        free(cred); free(token_json); free(access); free(refresh); free(account); free(conexao); free(credencial);
        return 1;
    }
    free(cred);

    char *j0 = json_set_url_default(json_base);
    char *j1 = j0 ? json_set_string_first(j0, "conexao", conexao) : NULL;
    char *j2 = j1 ? json_set_string_first(j1, "tipo", "oauth") : NULL;
    char *j3 = j2 ? json_set_autenticacao_oauth(j2, credencial) : NULL;
    free(j0); free(j1); free(j2);
    if (!j3) {
        fprintf(stderr, "criar-conta-llm: erro ao montar preset OAuth.\n");
        free(token_json); free(access); free(refresh); free(account); free(conexao); free(credencial);
        return 1;
    }

    char caminho_preset[PATH_MAX * 4], caminho_llm[PATH_MAX * 4];
    snprintf(caminho_preset, sizeof(caminho_preset), "%s/%s.json", dir_contas, conexao);
    snprintf(caminho_llm, sizeof(caminho_llm), "%s/llm.json", dir_contas);
    if (escrever_arquivo(caminho_preset, j3, 0644) != 0 || escrever_arquivo(caminho_llm, j3, 0644) != 0) {
        fprintf(stderr, "criar-conta-llm: erro ao salvar preset OAuth.\n");
        free(j3); free(token_json); free(access); free(refresh); free(account); free(conexao); free(credencial);
        return 1;
    }

    printf("Conta OAuth criada: contas-llm/%s.json\n", conexao);
    printf("Credenciais salvas em: secretos/%s.json\n", credencial);
    printf("llm.json atualizado: contas-llm/llm.json\n");
    printf("Credencial OAuth compartilhada por provedor+conta; outros modelos podem apontar para: %s\n", credencial);

    free(j3); free(token_json); free(access); free(refresh); free(account); free(conexao); free(credencial);
    return 0;
}

static void uso(void)
{
    fputs(
        "Uso:\n"
        "  pp --criar-conta-llm=<API_KEY|auth|oauth>\n"
        "  pp --criar-conta-llm --ajuda:<grupo>\n"
        "\n"
        "Descrição:\n"
        "  Cria um preset de conta LLM a partir de contas-llm/llm.json e salva\n"
        "  os segredos correspondentes no diretório secretos/.\n"
        "\n"
        "Grupos de ajuda: uso, modos, arquivos, exemplos.\n"
        "\n"
        "1) Uso e ajuda [uso ajuda]\n"
        "  --ajuda, --help, -h            Exibe esta ajuda\n"
        "  --ajuda:<grupo>                Filtra um grupo. Ex: pp --criar-conta-llm --ajuda:modos\n"
        "\n"
        "2) Modos de operação [modos entrada]\n"
        "  <API_KEY>                      Modo key: salva a chave em secretos/<conexao>.txt\n"
        "  auth                           Modo OAuth/PKCE com callback local\n"
        "  oauth                          Alias de auth\n"
        "  Exemplo com todas as flags do grupo:\n"
        "    pp --criar-conta-llm=sk-abc123  (key) ou pp --criar-conta-llm=auth (OAuth)\n"
        "\n"
        "3) Arquivos alterados [arquivos]\n"
        "  contas-llm/<conexao>.json      Preset da conta\n"
        "  secretos/<conexao>.txt         Chave secreta, quando modo key\n"
        "  secretos/<arquivo>.json        Tokens OAuth, quando modo auth\n"
        "  contas-llm/llm.json            Atualizado para a nova conexão\n"
        "\n"
        "4) Exemplos [exemplos]\n"
        "  pp --criar-conta-llm=sk-abc123...\n"
        "  pp --criar-conta-llm=auth\n"
        "  pp --criar-conta-llm=oauth\n",
        stdout);
}

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

    const char *api_key = NULL;
    const char *modelo_override = NULL;
    int modo_auth = 0;

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--ajuda") == 0 || strcmp(argv[i], "-h") == 0) {
            uso(); return 0;
        }
        if (strcmp(argv[i], "auth") == 0 || strcmp(argv[i], "oauth") == 0) {
            modo_auth = 1; continue;
        }
        if (strncmp(argv[i], "--modelo=", 9) == 0) {
            modelo_override = argv[i] + 9;
            continue;
        }
        if (argv[i][0] == '-' && argv[i][1] == '-') {
            fprintf(stderr, "criar-conta-llm: flag desconhecida: %s\n", argv[i]);
            uso(); return 1;
        }
        if (!api_key) api_key = argv[i];
        else { fprintf(stderr, "criar-conta-llm: argumento extra: %s\n", argv[i]); uso(); return 1; }
    }

    if (!modo_auth && !api_key) {
        fprintf(stderr, "criar-conta-llm: chave/API_KEY obrigatória no modo key.\n");
        uso(); return 1;
    }

    char raiz[PATH_MAX];
    if (raiz_projeto(raiz, sizeof(raiz)) != 0) {
        fprintf(stderr, "criar-conta-llm: não foi possível detectar a raiz do projeto.\n");
        return 1;
    }

    char dir_contas_base[PATH_MAX * 2];
    resolver_subdir_sistema(raiz, "contas-llm", dir_contas_base, sizeof(dir_contas_base));
    char caminho_base[PATH_MAX * 2];
    snprintf(caminho_base, sizeof(caminho_base), "%s/llm.json", dir_contas_base);
    char *json = ler_arquivo(caminho_base);
    if (!json) {
        fprintf(stderr, "criar-conta-llm: não foi possível ler %s\n", caminho_base);
        return 1;
    }

    char *modelo = json_get_string(json, "modelo");
    char *provedor = json_get_string(json, "provedor");
    char *conta = json_get_string(json, "conta");
    if (!modelo || !provedor || !conta) {
        fprintf(stderr, "criar-conta-llm: llm.json incompleto (precisa modelo, provedor e conta).\n");
        free(json); free(modelo); free(provedor); free(conta);
        return 1;
    }

    if (modelo_override) {
        free(modelo);
        modelo = strdup(modelo_override);
        if (!modelo) { free(json); free(provedor); free(conta); return 1; }
    }

    if (modo_auth) {
        int rc = criar_conta_auth(json, raiz, modelo, provedor, conta);
        free(json); free(modelo); free(provedor); free(conta);
        return rc;
    }

    char *conexao = conexao_canonica(modelo, provedor, conta);
    if (!conexao) {
        fprintf(stderr, "criar-conta-llm: erro ao gerar conexão canônica.\n");
        free(json); free(modelo); free(provedor); free(conta);
        return 1;
    }

    /* Resolve a chave: se for nome de arquivo existente em secretos/, usa a referência; senão é a chave nova */
    char *chave_ref = resolver_chave(raiz, api_key, conexao);
    if (!chave_ref) {
        fprintf(stderr, "criar-conta-llm: erro ao resolver chave.\n");
        free(json); free(modelo); free(provedor); free(conta); free(conexao);
        return 1;
    }

    char *j1 = json_set_string_first(json, "conexao", conexao);
    char *j2 = j1 ? json_set_string_first(j1, "tipo", "key") : NULL;
    char *j3 = j2 ? json_set_autenticacao_bearer(j2, conexao) : NULL;
    free(json); free(j1); free(j2);
    if (!j3) {
        fprintf(stderr, "criar-conta-llm: erro ao atualizar JSON.\n");
        free(modelo); free(provedor); free(conta); free(conexao); free(chave_ref);
        return 1;
    }

    char dir_contas[PATH_MAX * 2], dir_secretos[PATH_MAX * 2];
    /* Escrita sempre local */
    snprintf(dir_contas, sizeof(dir_contas), "%s/contas-llm", raiz);
    snprintf(dir_secretos, sizeof(dir_secretos), "%s/secretos", raiz);
    if (garantir_dir(dir_contas, 0775) != 0 || garantir_dir(dir_secretos, 0700) != 0) {
        fprintf(stderr, "criar-conta-llm: não foi possível criar diretórios necessários.\n");
        free(modelo); free(provedor); free(conta); free(conexao); free(chave_ref); free(j3);
        return 1;
    }

    char caminho_preset[PATH_MAX * 4], caminho_llm[PATH_MAX * 4];
    snprintf(caminho_preset, sizeof(caminho_preset), "%s/%s.json", dir_contas, conexao);
    snprintf(caminho_llm, sizeof(caminho_llm), "%s/llm.json", dir_contas);

    int ja_existe = (access(caminho_preset, F_OK) == 0);
    if (ja_existe) {
        fprintf(stderr, "criar-conta-llm: atualizando conta existente: contas-llm/%s.json\n", conexao);
    } else if (conta_existe_na_biblioteca(raiz, conexao)) {
        fprintf(stderr, "criar-conta-llm: conta já existe na biblioteca: core/biblioteca-contas-llm/%s.json\n", conexao);
        free(modelo); free(provedor); free(conta); free(conexao); free(chave_ref); free(j3);
        return 1;
    }

    if (escrever_arquivo(caminho_preset, j3, 0644) != 0) {
        fprintf(stderr, "criar-conta-llm: não foi possível salvar %s\n", caminho_preset);
        free(modelo); free(provedor); free(conta); free(conexao); free(chave_ref); free(j3);
        return 1;
    }
    if (escrever_arquivo(caminho_llm, j3, 0644) != 0) {
        fprintf(stderr, "criar-conta-llm: não foi possível atualizar %s\n", caminho_llm);
        free(modelo); free(provedor); free(conta); free(conexao); free(chave_ref); free(j3);
        return 1;
    }

    /* Salva segredo apenas se for chave nova (não referência a arquivo existente) */
    struct stat st_chave;
    char caminho_chave[PATH_MAX * 2];
    snprintf(caminho_chave, sizeof(caminho_chave), "%s/%s", dir_secretos, api_key);
    int eh_arquivo_existente = (stat(caminho_chave, &st_chave) == 0 && S_ISREG(st_chave.st_mode));
    if (!eh_arquivo_existente) {
        /* Tenta com .txt */
        size_t arglen = strlen(api_key);
        if (arglen < PATH_MAX * 2 - 5) {
            char arg_txt[PATH_MAX * 2];
            snprintf(arg_txt, sizeof(arg_txt), "%s.txt", api_key);
            snprintf(caminho_chave, sizeof(caminho_chave), "%s/%s", dir_secretos, arg_txt);
            eh_arquivo_existente = (stat(caminho_chave, &st_chave) == 0 && S_ISREG(st_chave.st_mode));
        }
    }
    
    if (!eh_arquivo_existente) {
        char caminho_secreto[PATH_MAX * 4];
        snprintf(caminho_secreto, sizeof(caminho_secreto), "%s/%s.txt", dir_secretos, conexao);
        if (escrever_arquivo(caminho_secreto, api_key, 0600) != 0) {
            fprintf(stderr, "criar-conta-llm: não foi possível salvar %s\n", caminho_secreto);
            free(modelo); free(provedor); free(conta); free(conexao); free(chave_ref); free(j3);
            return 1;
        }
        printf("Chave salva em: secretos/%s.txt\n", conexao);
    } else {
        printf("Usando chave existente: secretos/%s (referenciado como %s)\n", api_key, chave_ref);
    }

    if (ja_existe) {
        printf("Conta LLM atualizada: contas-llm/%s.json\n", conexao);
    } else {
        printf("Conta LLM criada: contas-llm/%s.json\n", conexao);
    }
    printf("llm.json atualizado: contas-llm/llm.json\n");
    printf("Modelo: %s | Provedor: %s | Conta: %s | Tipo: key\n", modelo, provedor, conta);

    free(modelo); free(provedor); free(conta); free(conexao); free(chave_ref); free(j3);
    return 0;
}
