/*
 * testar-contas-llm - testa interativamente uma conta LLM
 * Uso: pp --testar-contas-llm [--conta-llm=arquivo.json]
 */

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

typedef enum { ORIGEM_BIBLIOTECA = 1, ORIGEM_SISTEMA = 2 } OrigemConta;

typedef struct {
    char nome[PATH_MAX];
    char arquivo[PATH_MAX];
    char caminho[PATH_MAX];
    char dir[PATH_MAX];
    OrigemConta origem;
    int gratuita;
} Conta;

typedef struct { int code; char *out; char *err; } Resultado;

static void uso(void)
{
    fputs(
        "Uso:\n"
        "  pp --testar-contas-llm [--conta-llm=<arquivo.json>] [--tentativas=<N>] [--espera=<S>]\n"
        "  pp --testar-contas-llm --free [--tentativas=<N>] [--espera=<S>]\n"
        "  pp --testar-contas-llm --ajuda:<grupo>\n"
        "\n"
        "Descrição:\n"
        "  Testa a conexão de uma conta LLM enviando um prompt simples e verificando\n"
        "  se a API responde corretamente. Sem --conta-llm e sem --free, mostra uma\n"
        "  lista em três blocos: biblioteca de contas, contas-llm/ do sistema atual\n"
        "  (incluindo llm.json) e contas gratuitas (free) configuradas.\n"
        "\n"
        "Grupos de ajuda: uso, entrada, processo, exemplos.\n"
        "\n"
        "1) Uso e ajuda [uso ajuda]\n"
        "  --ajuda, --help, -h            Exibe esta ajuda\n"
        "  --ajuda:<grupo>                Filtra um grupo. Ex: pp --testar-contas-llm --ajuda:entrada\n"
        "\n"
        "2) Entrada [entrada]\n"
        "  --conta-llm=<arquivo.json>     Testa uma conta específica sem listar\n"
        "  --free                        Testa só as contas gratuitas, repetindo cada\n"
        "                                uma até funcionar ou esgotar --tentativas\n"
        "  --tentativas=<N>              Nº de tentativas por conta (padrão 1; com\n"
        "                                --free o padrão é 12). Erro definitivo (ex.\n"
        "                                401/sem saldo) não repete\n"
        "  --espera=<S>                  Segundos entre tentativas (padrão 60)\n"
        "  Sem flag                       Lista contas em três blocos (biblioteca,\n"
        "                                contas-llm/ e gratuitas) para seleção\n"
        "  Exemplo com todas as flags do grupo:\n"
        "    pp --testar-contas-llm --conta-llm=gpt.json\n"
        "    pp --testar-contas-llm --free --tentativas=20 --espera=90\n"
        "\n"
        "3) Processo [processo]\n"
        "  Envia o prompt de teste 'Teste de conexão LLM do sisc. Responda exatamente: OK' e mostra resposta/métricas.\n"
        "\n"
        "4) Exemplos [exemplos]\n"
        "  pp --testar-contas-llm\n"
        "  pp --testar-contas-llm --conta-llm=gpt.json\n"
        "  pp --testar-contas-llm --free\n"
        "  pp --testar-contas-llm --free --tentativas=20 --espera=90\n",
        stdout);
}

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 eh_dir(const char *p)
{
    struct stat st;
    return p && stat(p, &st) == 0 && S_ISDIR(st.st_mode);
}

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

static void resolver_dir(const char *raiz, const char *nome, char *out, size_t tam)
{
    snprintf(out, tam, "%s/%s", raiz, nome);
}

static int raiz_sisc_por_executavel_cmd(char *out, size_t tam)
{
    char exe[PATH_MAX];
    ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
    if (n <= 0) return -1;
    exe[n] = '\0';

    char *slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0';                 /* .../core/comandos */
    slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0';                 /* .../core */
    slash = strrchr(exe, '/');
    if (!slash) return -1;
    *slash = '\0';                 /* .../sisc */

    snprintf(out, tam, "%s", exe);
    return 0;
}

static void resolver_raizes(const char *raiz_sistema, char *raiz_sisc, size_t tam)
{
    if (raiz_sisc_por_executavel_cmd(raiz_sisc, tam) == 0) return;

    snprintf(raiz_sisc, tam, "%s", raiz_sistema);
    char *slash = strrchr(raiz_sisc, '/');
    if (slash && slash != raiz_sisc) *slash = '\0';
}

static int cmp_conta(const void *a,const void *b){const Conta*ca=a,*cb=b;return strcasecmp(ca->arquivo,cb->arquivo);} 

static int json_eh_free(const char *caminho);

static int listar_contas_dir(const char *dir_contas, OrigemConta origem, int incluir_llm, Conta **out, int *total_out)
{
    DIR *d=opendir(dir_contas); if(!d) return -1;
    int cap=32,total=0; Conta *v=calloc((size_t)cap,sizeof(Conta)); if(!v){closedir(d);return -1;}
    struct dirent *e;
    while((e=readdir(d))){
        const char*n=e->d_name;
        if(n[0]=='.'||!termina_com(n,".json")) continue;
        if(!incluir_llm && strcmp(n,"llm.json")==0) continue;
        char p[PATH_MAX*2]; snprintf(p,sizeof(p),"%s/%s",dir_contas,n);
        if(!eh_arquivo_regular(p)) continue;
        if(total>=cap){cap*=2; Conta*nv=realloc(v,(size_t)cap*sizeof(Conta)); 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",p);
        snprintf(v[total].dir,sizeof(v[total].dir),"%s",dir_contas);
        snprintf(v[total].nome,sizeof(v[total].nome),"%s",n);
        v[total].nome[strlen(v[total].nome)-5]='\0';
        v[total].origem=origem;
        v[total].gratuita=json_eh_free(p);
        total++;
    }
    closedir(d); qsort(v,(size_t)total,sizeof(Conta),cmp_conta); *out=v; *total_out=total; return 0;
}

static int normalizar_conta(const char *in, char *out, size_t tam)
{
    if(!in||!*in||!out||tam<8) return -1;
    while(isspace((unsigned char)*in)) in++;
    if(strchr(in,'/')||strchr(in,'\\')||strstr(in,"..")) return -1;
    snprintf(out,tam,"%s",in);
    size_t n=strlen(out); while(n>0&&isspace((unsigned char)out[n-1])) out[--n]='\0';
    if(n==0) return -1;
    if(!termina_com(out,".json")){ if(n+5>=tam) return -1; strcat(out,".json"); }
    for(const char*p=out;*p;p++) if(!(isalnum((unsigned char)*p)||*p=='.'||*p=='_'||*p=='-')) return -1;
    return 0;
}

static Conta *encontrar_conta(Conta *contas, int total, const char *arquivo)
{ for(int i=0;i<total;i++) if(strcmp(contas[i].arquivo,arquivo)==0) return &contas[i]; return NULL; }

/* Conta gratuita = preset com "categoria": "gratuita" */
static int json_eh_free(const char *caminho)
{
    FILE *fp = fopen(caminho, "rb");
    if (!fp) return 0;
    char buf[8192];
    size_t n = fread(buf, 1, sizeof(buf) - 1, fp);
    fclose(fp);
    if (n == 0 || n >= sizeof(buf) - 1) return 0;
    buf[n] = '\0';
    const char *p = strstr(buf, "\"categoria\"");
    if (!p) return 0;
    p += 11;
    while (*p && (*p == ' ' || *p == '\t' || *p == ':')) p++;
    return strncmp(p, "\"gratuita\"", 10) == 0;
}

/* Erro definitivo (nao adianta repetir) x transitorio (vale repetir) */
static int erro_transitorio(const char *err)
{
    if (!err || !err[0]) return 1;
    if (strstr(err, "401") || strstr(err, "403") ||
        strstr(err, "CreditsError") || strstr(err, "Insufficient") ||
        strstr(err, "Unauthorized") || strstr(err, "invalid_api_key") ||
        strstr(err, "nao foi possivel preparar conta temporaria"))
        return 0;
    return 1;
}

static int ler_numero(int total)
{ char l[64]; printf("Digite o numero da conta a testar: "); fflush(stdout); if(!fgets(l,sizeof(l),stdin)) return -1; char*p=l; while(isspace((unsigned char)*p))p++; if(!isdigit((unsigned char)*p)) return -1; int n=atoi(p); return n>=1&&n<=total?n:-1; }

static char *ler_fd(int fd)
{
    size_t cap=4096,len=0; char *buf=malloc(cap); if(!buf) return NULL; buf[0]='\0';
    char tmp[1024]; ssize_t n;
    while((n=read(fd,tmp,sizeof(tmp)))>0){ if(len+(size_t)n+1>=cap){cap=(len+(size_t)n+1)*2; char*nb=realloc(buf,cap); if(!nb){free(buf);return NULL;} buf=nb;} memcpy(buf+len,tmp,(size_t)n); len+=(size_t)n; buf[len]='\0'; }
    return buf;
}

static int copiar_arquivo_bin(const char *origem, const char *destino)
{
    FILE *in = fopen(origem, "rb");
    if (!in) return -1;
    FILE *out = fopen(destino, "wb");
    if (!out) { fclose(in); return -1; }
    char buf[8192];
    size_t n;
    int ok = 1;
    while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
        if (fwrite(buf, 1, n, out) != n) { ok = 0; break; }
    }
    if (ferror(in)) ok = 0;
    fclose(in);
    if (fclose(out) != 0) ok = 0;
    return ok ? 0 : -1;
}

static Resultado testar_com_parallm(const char *arquivo, const char *raiz)
{
    Resultado r; r.code=1; r.out=strdup(""); r.err=strdup("");
    int outp[2], errp[2]; if(pipe(outp)!=0||pipe(errp)!=0){return r;}
    pid_t pid=fork();
    if(pid==0){
        chdir(raiz); close(outp[0]); close(errp[0]); dup2(outp[1],STDOUT_FILENO); dup2(errp[1],STDERR_FILENO); close(outp[1]); close(errp[1]);
        char bin[PATH_MAX*2];
        ssize_t l = readlink("/proc/self/exe", bin, sizeof(bin)-1);
        if (l > 0) { bin[l] = '\0'; char *s = strrchr(bin, '/'); if (s) strcpy(s + 1, "parallm"); }
        else snprintf(bin, sizeof(bin), "parallm");
        char arg[PATH_MAX+32]; snprintf(arg,sizeof(arg),"--conta-llm=%s",arquivo);
        char *argv[] = { bin, "Teste de conexão LLM do sisc. Responda exatamente: OK", "--sem-heb", arg, NULL };
        execv(bin, argv); _exit(127);
    }
    close(outp[1]); close(errp[1]); free(r.out); free(r.err); r.out=ler_fd(outp[0]); r.err=ler_fd(errp[0]); close(outp[0]); close(errp[0]);
    int st=0; waitpid(pid,&st,0); r.code = WIFEXITED(st)?WEXITSTATUS(st):1; if(!r.out)r.out=strdup(""); if(!r.err)r.err=strdup(""); return r;
}

static Resultado testar(const Conta *conta, const char *raiz, const char *dir_contas_sistema)
{
    if (conta->origem == ORIGEM_SISTEMA) return testar_com_parallm(conta->arquivo, raiz);

    char tmp_arq[PATH_MAX];
    char tmp_path[PATH_MAX * 2];
    snprintf(tmp_arq, sizeof(tmp_arq), ".teste-biblioteca-%ld-%s", (long)getpid(), conta->arquivo);
    snprintf(tmp_path, sizeof(tmp_path), "%s/%s", dir_contas_sistema, tmp_arq);

    Resultado r;
    if (copiar_arquivo_bin(conta->caminho, tmp_path) != 0) {
        r.code = 1;
        r.out = strdup("");
        char msg[PATH_MAX * 3];
        snprintf(msg, sizeof(msg), "nao foi possivel preparar conta temporaria em contas-llm/: %s\n", strerror(errno));
        r.err = strdup(msg);
        return r;
    }

    r = testar_com_parallm(tmp_arq, raiz);
    unlink(tmp_path);
    return r;
}

/* Testa ate obter OK ou esgotar as tentativas. Retorna 1 se pronta para uso. */
static int testar_ate_ok(const Conta *conta, const char *raiz, const char *dir_sistema,
                         int tentativas, int espera)
{
    for (int t = 1; t <= tentativas; t++) {
        printf("  [tentativa %d/%d] %s ...\n", t, tentativas, conta->arquivo);
        fflush(stdout);
        Resultado r = testar(conta, raiz, dir_sistema);
        if (r.code == 0 && r.out && r.out[0]) {
            printf("  [ok] %s pronta para uso.\nResposta: %s\n", conta->arquivo, r.out);
            free(r.out); free(r.err);
            return 1;
        }
        printf("  [falha] tentativa %d: codigo %d\n", t, r.code);
        if (r.err && r.err[0]) printf("  Erro: %.500s\n", r.err);
        int repetir = (t < tentativas) && erro_transitorio(r.err);
        free(r.out); free(r.err);
        if (!repetir) {
            if (t < tentativas)
                printf("  [definitivo] erro nao transitorio; sem novas tentativas.\n");
            return 0;
        }
        printf("  Aguardando %d s antes da proxima tentativa...\n", espera);
        fflush(stdout);
        sleep((unsigned int)espera);
    }
    return 0;
}

int main(int argc, char **argv)
{     if (restricao_verificar("testar-contas-llm") != 0) return 1; 
    char override[PATH_MAX]="";
    int modo_free = 0, tentativas = 0, espera = 60, tentativas_info = 0, espera_info = 0;
    for(int i=1;i<argc;i++){
        if(!strcmp(argv[i],"--help")||!strcmp(argv[i],"--ajuda")||!strcmp(argv[i],"-h")){uso();return 0;}
        if(!strcmp(argv[i],"--free")){ modo_free = 1; continue; }
        if(strncmp(argv[i],"--tentativas=",13)==0){ tentativas = atoi(argv[i]+13); tentativas_info = 1; continue; }
        if(strncmp(argv[i],"--espera=",9)==0){ espera = atoi(argv[i]+9); espera_info = 1; continue; }
        if(strncmp(argv[i],"--conta-llm=",12)==0){ if(normalizar_conta(argv[i]+12,override,sizeof(override))!=0){fprintf(stderr,"testar-contas-llm: conta invalida.\n");return 1;} continue; }
        uso(); return 1;
    }
    if(tentativas_info && (tentativas < 1 || tentativas > 100)){fprintf(stderr,"testar-contas-llm: --tentativas deve ser 1..100.\n");return 1;}
    if(espera_info && (espera < 0 || espera > 3600)){fprintf(stderr,"testar-contas-llm: --espera deve ser 0..3600.\n");return 1;}
    if(!tentativas_info) tentativas = modo_free ? 12 : 1;
    if(!override[0]){
        const char *explicita=getenv("SISC_CONTA_LLM_EXPLICITA");
        const char *e=getenv("SISC_CONTA_LLM");
        if(explicita&&*explicita&&e&&*e) normalizar_conta(e,override,sizeof(override));
    }

    char raiz[PATH_MAX]; if(!getcwd(raiz,sizeof(raiz))){perror("getcwd");return 1;}
    char raiz_sisc[PATH_MAX]; resolver_raizes(raiz, raiz_sisc, sizeof(raiz_sisc));

    char dir_sistema[PATH_MAX*2]; resolver_dir(raiz,"contas-llm",dir_sistema,sizeof(dir_sistema));
    if(!eh_dir(dir_sistema)){fprintf(stderr,"testar-contas-llm: diretorio nao encontrado: contas-llm\n");return 1;}

    char dir_bib[PATH_MAX*2];
    snprintf(dir_bib,sizeof(dir_bib),"%s/core/biblioteca-contas",raiz_sisc);
    if(!eh_dir(dir_bib)) snprintf(dir_bib,sizeof(dir_bib),"%s/core/biblioteca-contas-llm",raiz_sisc);

    Conta *bib=NULL, *sis=NULL; int total_bib=0,total_sis=0;
    if(eh_dir(dir_bib)) listar_contas_dir(dir_bib,ORIGEM_BIBLIOTECA,1,&bib,&total_bib);
    if(listar_contas_dir(dir_sistema,ORIGEM_SISTEMA,1,&sis,&total_sis)!=0){fprintf(stderr,"testar-contas-llm: erro ao listar contas-llm/.\n");free(bib);return 1;}

    if(total_bib==0 && total_sis==0){fprintf(stderr,"testar-contas-llm: nenhuma conta encontrada na biblioteca ou em contas-llm/.\n");free(bib);free(sis);return 1;}

    /* Modo --free: testa todas as gratuitas do sistema (e as só da biblioteca),
     * repetindo cada uma até ficar pronta ou esgotar as tentativas. */
    if(modo_free){
        Conta *alvos[256]; int n_alvos = 0;
        for(int i=0;i<total_sis && n_alvos<256;i++)
            if(sis[i].gratuita) alvos[n_alvos++] = &sis[i];
        for(int i=0;i<total_bib && n_alvos<256;i++)
            if(bib[i].gratuita && !encontrar_conta(sis,total_sis,bib[i].arquivo))
                alvos[n_alvos++] = &bib[i];
        if(n_alvos==0){fprintf(stderr,"testar-contas-llm: nenhuma conta gratuita configurada.\n");free(bib);free(sis);return 1;}
        printf("Testando %d conta(s) gratuita(s) (%d tentativas, espera %d s):\n\n", n_alvos, tentativas, espera);
        int prontas = 0;
        char pendentes[4096] = "";
        for(int i=0;i<n_alvos;i++){
            printf("[%d/%d] %s (%s)\n", i+1, n_alvos, alvos[i]->arquivo,
                   alvos[i]->origem==ORIGEM_BIBLIOTECA?"biblioteca":"contas-llm");
            if(testar_ate_ok(alvos[i],raiz,dir_sistema,tentativas,espera)) prontas++;
            else {
                if(pendentes[0]) strncat(pendentes, ", ", sizeof(pendentes)-strlen(pendentes)-1);
                strncat(pendentes, alvos[i]->arquivo, sizeof(pendentes)-strlen(pendentes)-1);
            }
            printf("\n");
        }
        printf("Resumo free: %d/%d pronta(s) para uso.\n", prontas, n_alvos);
        if(pendentes[0]) printf("Pendentes: %s\n", pendentes);
        free(bib); free(sis);
        return prontas == n_alvos ? 0 : 1;
    }

    Conta *sel=NULL;
    if(override[0]){
        sel=encontrar_conta(sis,total_sis,override);
        if(!sel) sel=encontrar_conta(bib,total_bib,override);
        if(!sel){fprintf(stderr,"testar-contas-llm: conta nao encontrada na biblioteca nem em contas-llm/: %s\n",override);free(bib);free(sis);return 1;}
    }
    else {
        /* Exibição em três blocos: biblioteca, sistema e gratuitas. */
        int cap_sel = total_bib + total_sis;
        Conta **ordem = malloc(sizeof(Conta*) * (size_t)(cap_sel > 0 ? cap_sel : 1));
        if(!ordem){fprintf(stderr,"testar-contas-llm: erro de memória.\n");free(bib);free(sis);return 1;}
        int n_ordem = 0, numero = 1;
        printf("Contas LLM disponiveis:\n\n");
        printf("Biblioteca de contas:\n");
        int vazio = 1;
        for(int i=0;i<total_bib;i++) if(!bib[i].gratuita){ printf("%2d) %s\n", numero++, bib[i].arquivo); ordem[n_ordem++] = &bib[i]; vazio = 0; }
        if(vazio) printf("  (nenhuma conta encontrada)\n");
        printf("\nContas do sistema (contas-llm/, incluindo llm.json):\n");
        vazio = 1;
        for(int i=0;i<total_sis;i++) if(!sis[i].gratuita){ printf("%2d) %s\n", numero++, sis[i].arquivo); ordem[n_ordem++] = &sis[i]; vazio = 0; }
        if(vazio) printf("  (nenhuma conta encontrada)\n");
        printf("\nContas gratuitas (free) configuradas:\n");
        vazio = 1;
        for(int i=0;i<total_sis;i++) if(sis[i].gratuita){ printf("%2d) %s  [contas-llm]\n", numero++, sis[i].arquivo); ordem[n_ordem++] = &sis[i]; vazio = 0; }
        for(int i=0;i<total_bib;i++) if(bib[i].gratuita && !encontrar_conta(sis,total_sis,bib[i].arquivo)){ printf("%2d) %s  [biblioteca]\n", numero++, bib[i].arquivo); ordem[n_ordem++] = &bib[i]; vazio = 0; }
        if(vazio) printf("  (nenhuma conta encontrada)\n");
        printf("\n");
        int n=ler_numero(n_ordem);
        if(n<1){fprintf(stderr,"testar-contas-llm: numero invalido.\n");free(ordem);free(bib);free(sis);return 1;}
        sel=ordem[n-1];
        free(ordem);
    }

    printf("\nTestando conta: %s (%s)\n", sel->arquivo, sel->origem==ORIGEM_BIBLIOTECA?"biblioteca":"contas-llm");
    if(tentativas > 1){
        int ok = testar_ate_ok(sel,raiz,dir_sistema,tentativas,espera);
        free(bib); free(sis);
        return ok ? 0 : 1;
    }
    Resultado r=testar(sel,raiz,dir_sistema);
    if(r.code==0 && r.out && r.out[0]){ printf("[ok] Conexao LLM funcionando.\nResposta: %s\n", r.out); free(r.out); free(r.err); free(bib); free(sis); return 0; }
    printf("[falha] Conexao LLM nao respondeu corretamente.\nCodigo de saida: %d\n", r.code);
    if(r.err&&r.err[0]) printf("Erro:\n%s\n", r.err);
    if(r.out&&r.out[0]) printf("Saida:\n%s\n", r.out);
    free(r.out); free(r.err); free(bib); free(sis); return 1;
}
