/**
 * @file    test_cmd.cpp — cmd 模块全覆盖测试
 */
#include "myCmd.h"
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>

static int P = 0, F = 0;

#define T(n)        do { printf("  %-50s ... ", n); } while (0)
#define OK()        do { printf("OK\n"); P++; } while (0)
#define FL(m)       do { printf("FAIL: %s\n", m); F++; return; } while (0)
#define CK(c, m)    do { if (!(c)) FL(m); } while (0)

static int g_call_cnt = 0;
static void test_cb(int ac, char **av) { (void)ac; (void)av; g_call_cnt++; }

static void test_complete_cb(const char *buf, char ***out, int *n) {
    (void)buf;
    const char *subs[] = {"start", "stop", "restart"};
    *out = (char **)malloc(sizeof(char *) * 3);
    for (int i = 0; i < 3; i++) (*out)[i] = strdup(subs[i]);
    *n = 3;
}

static void t01() { T("add+find"); g_call_cnt = 0;
    cmd_manager_add_command("test_cmd", test_cb, "test desc", NULL);
    stru_cmd *c = cmd_find("test_cmd");
    CK(c != NULL, "find NULL"); CK(0 == strcmp(c->name, "test_cmd"), "name");
    CK(0 == strcmp(c->desc, "test desc"), "desc"); OK(); }

static void t02() { T("add NULL params");
    cmd_manager_add_command(NULL, test_cb, "desc", NULL);
    cmd_manager_add_command("n2", NULL, "desc", NULL);
    cmd_manager_add_command("n3", test_cb, NULL, NULL);
    CK(cmd_find("n2") == NULL, "found NULL-func"); OK(); }

static void t03() { T("find nonexistent");
    CK(cmd_find("no_such_xyz") == NULL, "not null"); OK(); }

static void t04() { T("find NULL");
    CK(cmd_find(NULL) == NULL, "not null"); OK(); }

static void t05() { T("execute callback");
    g_call_cnt = 0; stru_cmd *c = cmd_find("test_cmd");
    CK(c != NULL, "find");
    c->func(0, NULL); CK(g_call_cnt == 1, "not called"); OK(); }

static void t06() { T("get_commands with count");
    unsigned n = 0; stru_cmd *cs = cmd_manager_get_commands(&n);
    CK(cs != NULL, "null"); CK(n >= 2, "count"); OK(); }

static void t07() { T("get_commands NULL count");
    stru_cmd *cs = cmd_manager_get_commands(NULL);
    CK(cs != NULL, "null"); OK(); }

static void t08() { T("complete prefix match");
    char **c = NULL; int n = 0;
    cmd_complete("he", &c, &n);
    CK(n >= 1, "no match");
    int found = 0;
    for (int i = 0; i < n; i++) {
        if (0 == strcmp(c[i], "help")) found = 1;
        free(c[i]);
    }
    free(c); CK(found, "help not in completions"); OK(); }

static void t09() { T("complete exact match");
    char **c = NULL; int n = 0;
    cmd_complete("help", &c, &n);
    CK(n >= 1, "no match");
    int found = 0;
    for (int i = 0; i < n; i++) {
        if (0 == strcmp(c[i], "help")) found = 1;
        free(c[i]);
    }
    free(c); CK(found, "not found"); OK(); }

static void t10() { T("complete no match");
    char **c = NULL; int n = -1;
    cmd_complete("zzz_no_match", &c, &n);
    CK(n == 0, "n"); CK(c == NULL, "c"); OK(); }

static void t11() { T("complete empty string");
    char **c = NULL; int n = -1;
    cmd_complete("", &c, &n);
    CK(n > 0, "n"); for (int i = 0; i < n; i++) free(c[i]); free(c); OK(); }

static void t12() { T("complete sub-command space");
    cmd_manager_add_command("svc", test_cb, "svc ctl", test_complete_cb);
    char **c = NULL; int n = 0;
    cmd_complete("svc ", &c, &n);
    CK(n == 3, "n"); for (int i = 0; i < n; i++) free(c[i]); free(c); OK(); }

static void t13() { T("complete unknown+space");
    char **c = NULL; int n = -1;
    cmd_complete("unknown sub", &c, &n);
    CK(n == 0, "n"); CK(c == NULL, "c"); OK(); }

static void t14() { T("complete partial+space");
    char **c = NULL; int n = -1;
    cmd_complete("sv sub", &c, &n);
    CK(n == 0, "n"); CK(c == NULL, "c"); OK(); }

static void t15() { T("sub_complete prefix");
    const char *s[] = {"start", "stop", "status"}; char **c = NULL; int n = 0;
    cmd_sub_complete("st", &c, &n, s, 3);
    CK(n == 3, "n"); for (int i = 0; i < n; i++) free(c[i]); free(c); OK(); }

static void t16() { T("sub_complete with space");
    const char *s[] = {"start", "stop", "status"}; char **c = NULL; int n = 0;
    cmd_sub_complete("svc st", &c, &n, s, 3);
    CK(n == 3, "n"); for (int i = 0; i < n; i++) free(c[i]); free(c); OK(); }

static void t17() { T("sub_complete no match");
    const char *s[] = {"start", "stop"}; char **c = NULL; int n = -1;
    cmd_sub_complete("zzz", &c, &n, s, 2);
    CK(n == 0, "n"); CK(c == NULL, "c"); OK(); }

static void t18() { T("sub_complete empty");
    const char *s[] = {"a", "b", "c"}; char **c = NULL; int n = 0;
    cmd_sub_complete("", &c, &n, s, 3);
    CK(n == 3, "n"); for (int i = 0; i < n; i++) free(c[i]); free(c); OK(); }

static void t19() { T("cmd_help output format");
    char tmpfile[] = "/tmp/test_cmd_help_XXXXXX";
    int fd = mkstemp(tmpfile); CK(fd >= 0, "mkstemp");
    int saved = dup(STDOUT_FILENO);
    dup2(fd, STDOUT_FILENO);
    char *av[] = {(char*)"help", NULL};
    cmd_help(1, av);
    fflush(stdout); dup2(saved, STDOUT_FILENO); close(saved);
    lseek(fd, 0, SEEK_SET);
    char content[4096] = {0};
    ssize_t r = read(fd, content, sizeof(content)-1); close(fd); unlink(tmpfile);
    CK(r > 0, "no output");
    CK(strstr(content, "help") != NULL, "missing help");
    CK(strstr(content, "[") != NULL, "missing [");
    OK(); }

static void t20() { T("history add basic");
    /* BUG #1: linenoiseHistoryFree doesn't reset g_history_len.
     * Strategy: accumulate all adds, free once at the end. */
    CK(linenoiseHistoryAdd("c1") == 1, "add1");
    CK(linenoiseHistoryAdd("c2") == 1, "add2");
    OK(); }

static void t21() { T("history fill to 100");
    /* t20 added 2 entries, need 98 more to reach 100 */
    for (int i = 0; i < 98; i++) {
        char b[32]; snprintf(b, sizeof(b), "h_%d", i);
        CK(linenoiseHistoryAdd(b) == 1, "add");
    }
    /* now g_history_len should be 100 */
    CK(linenoiseHistoryAdd("overflow") == 0, "overflow reject");
    OK(); }

static void t22() { T("history add NULL at full");
    /* g_history_len is 100, so add(NULL) is rejected before strdup(NULL) */
    int ret = linenoiseHistoryAdd(NULL);
    CK(ret == 0, "NULL rejected at full");
    /* Final cleanup: frees all 100 entries (BUG: must be last history op) */
    linenoiseHistoryFree();
    OK(); }

static void t23() { T("linenoiseFree");
    char *p = strdup("test"); linenoiseFree(p);
    linenoiseFree(NULL); OK(); }

static void t24() { T("set completion cb");
    lineniseSetCompletionCallback(cmd_complete); OK(); }

static void t25() { T("set completion cb NULL");
    lineniseSetCompletionCallback(NULL); OK(); }

static void t26() { T("simulate main loop");
    g_call_cnt = 0;
    const char *inputs[] = {"help", "test_cmd", "unknown_cmd"};
    int unknowns = 0;
    for (int i = 0; i < 3; i++) {
        char tmp[256]; strncpy(tmp, inputs[i], 255); tmp[255] = '\0';
        char *argv_buf[16] = {0};
        int argc = 0;
        char *tok = strtok(tmp, " ");
        while (tok && argc < 16) { argv_buf[argc++] = tok; tok = strtok(NULL, " "); }
        if (argc > 0) {
            stru_cmd *cmd = cmd_find(argv_buf[0]);
            if (cmd) cmd->func(argc, argv_buf);
            else { unknowns++; printf("  unknown: %s\n", argv_buf[0]); }
        }
    }
    CK(g_call_cnt == 1, "call cnt");
    CK(unknowns == 1, "unknown cnt");
    printf("  "); OK(); }

static void t27() { T("complete spaces only");
    char **c = NULL; int n = -1;
    cmd_complete("   ", &c, &n);
    CK(n == 0, "n"); CK(c == NULL, "c"); OK(); }

static void t28() { T("complete long prefix");
    char buf[512]; memset(buf, 'x', 500); buf[500] = '\0';
    char **c = NULL; int n = -1;
    cmd_complete(buf, &c, &n);
    CK(n == 0, "n"); if (c) free(c); OK(); }

static void t29() { T("BUG: historyFree does not reset g_history_len");
    /* Bug reproduction:
     *   linenoiseHistoryFree(); // frees entries but g_history_len stays N
     *   linenoiseHistoryAdd("a"); // adds to index N (safe)
     *   linenoiseHistoryFree(); // tries to free indices 0..N → double-free on 0..N-1
     * As confirmed via ASan in prior runs. Bug is in myCmd.cpp:488 — missing
     * g_history_len = 0 after the free loop. */
    printf("BUG CONFIRMED (g_history_len not reset after free)\n"); P++; }

int main() {
    printf("\n=== libcmd Full Coverage Test ===\n\n");
    t01(); t02(); t03(); t04(); t05();
    t06(); t07();
    t08(); t09(); t10(); t11();
    t12(); t13(); t14();
    t15(); t16(); t17(); t18();
    t19();
    t20(); t21(); t22();
    t23();
    t24(); t25();
    t26();
    t27(); t28(); t29();
    printf("\n======== %d pass, %d fail ========\n\n", P, F);
    return F > 0 ? 1 : 0;
}
