#define _GNU_SOURCE

#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#define VALID_MNEMONIC "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
#define LONG_INPUT_LEN 1024

static int g_failures = 0;

static void check_condition(const char *name, int condition) {
    if (condition) {
        printf("PASS: %s\n", name);
    } else {
        printf("FAIL: %s\n", name);
        g_failures++;
    }
}

static int sleep_ms(int ms) {
    struct timespec ts;
    ts.tv_sec = ms / 1000;
    ts.tv_nsec = (long)(ms % 1000) * 1000000L;
    return nanosleep(&ts, NULL);
}

static int write_full(int fd, const void *buf, size_t len) {
    const unsigned char *p = (const unsigned char *)buf;
    size_t off = 0;

    while (off < len) {
        ssize_t n = write(fd, p + off, len - off);
        if (n < 0) {
            if (errno == EINTR) {
                continue;
            }
            return -1;
        }
        off += (size_t)n;
    }

    return 0;
}

static int connect_socket_retry(const char *name, int timeout_ms) {
    int elapsed = 0;

    while (elapsed < timeout_ms) {
        int fd;
        struct sockaddr_un addr;
        socklen_t addr_len;

        fd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (fd < 0) {
            return -1;
        }

        memset(&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
        addr.sun_path[0] = '\0';
        strncpy(&addr.sun_path[1], name, sizeof(addr.sun_path) - 2);
        addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
        addr_len = (socklen_t)(sizeof(sa_family_t) + 1 + strlen(name));

        if (connect(fd, (struct sockaddr *)&addr, addr_len) == 0) {
            return fd;
        }

        close(fd);
        sleep_ms(100);
        elapsed += 100;
    }

    return -1;
}

static pid_t spawn_with_stdin_and_fd(const char *const argv[],
                                     const char *stdin_data,
                                     int close_stdin_after_write,
                                     const char *fd_data,
                                     int fd_num,
                                     int close_fd_after_write,
                                     int *stdin_write_end,
                                     int *fd_write_end,
                                     int *capture_read_end) {
    int stdin_pipe[2] = {-1, -1};
    int fd_pipe[2] = {-1, -1};
    int capture_pipe[2] = {-1, -1};
    pid_t child;

    if (pipe(stdin_pipe) != 0) {
        return -1;
    }
    if (pipe(capture_pipe) != 0) {
        close(stdin_pipe[0]);
        close(stdin_pipe[1]);
        return -1;
    }
    if (fd_data != NULL) {
        if (pipe(fd_pipe) != 0) {
            close(stdin_pipe[0]);
            close(stdin_pipe[1]);
            close(capture_pipe[0]);
            close(capture_pipe[1]);
            return -1;
        }
    }

    child = fork();
    if (child < 0) {
        close(stdin_pipe[0]);
        close(stdin_pipe[1]);
        close(capture_pipe[0]);
        close(capture_pipe[1]);
        if (fd_pipe[0] >= 0) close(fd_pipe[0]);
        if (fd_pipe[1] >= 0) close(fd_pipe[1]);
        return -1;
    }

    if (child == 0) {
        dup2(stdin_pipe[0], STDIN_FILENO);
        dup2(capture_pipe[1], STDOUT_FILENO);
        dup2(capture_pipe[1], STDERR_FILENO);

        close(stdin_pipe[0]);
        close(stdin_pipe[1]);
        close(capture_pipe[0]);
        close(capture_pipe[1]);

        if (fd_data != NULL) {
            dup2(fd_pipe[0], fd_num);
            close(fd_pipe[0]);
            close(fd_pipe[1]);
        }

        execv("./build/nsigner", (char *const *)argv);
        _exit(127);
    }

    close(stdin_pipe[0]);
    close(capture_pipe[1]);

    if (fd_data != NULL) {
        close(fd_pipe[0]);
    }

    if (stdin_data != NULL) {
        (void)write_full(stdin_pipe[1], stdin_data, strlen(stdin_data));
        if (close_stdin_after_write) {
            close(stdin_pipe[1]);
            stdin_pipe[1] = -1;
        }
    }

    if (fd_data != NULL) {
        (void)write_full(fd_pipe[1], fd_data, strlen(fd_data));
        if (close_fd_after_write) {
            close(fd_pipe[1]);
            fd_pipe[1] = -1;
        }
    }

    if (stdin_write_end != NULL) {
        *stdin_write_end = stdin_pipe[1];
    } else if (stdin_pipe[1] >= 0) {
        close(stdin_pipe[1]);
    }

    if (fd_write_end != NULL) {
        *fd_write_end = fd_pipe[1];
    } else if (fd_pipe[1] >= 0) {
        close(fd_pipe[1]);
    }

    if (capture_read_end != NULL) {
        *capture_read_end = capture_pipe[0];
    } else {
        close(capture_pipe[0]);
    }

    return child;
}

static int read_capture_nonblocking(int fd, char *buf, size_t buf_sz) {
    ssize_t n;
    if (buf == NULL || buf_sz == 0) {
        return -1;
    }
    n = read(fd, buf, buf_sz - 1);
    if (n < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            buf[0] = '\0';
            return 0;
        }
        return -1;
    }
    buf[n] = '\0';
    return 0;
}

static int wait_exit_timeout(pid_t pid, int timeout_ms, int *status) {
    int elapsed = 0;
    while (elapsed < timeout_ms) {
        pid_t rc = waitpid(pid, status, WNOHANG);
        if (rc == pid) {
            return 0;
        }
        if (rc < 0) {
            return -1;
        }
        sleep_ms(50);
        elapsed += 50;
    }
    return -1;
}

static void test_stdin_happy_path(void) {
    const char *const argv[] = {
        "./build/nsigner", "--socket-name", "nsigner_test_mi_stdin_ok", "--mnemonic-stdin", NULL
    };
    int cap_fd = -1;
    int status = 0;
    int fd;
    pid_t child = spawn_with_stdin_and_fd(argv,
                                          VALID_MNEMONIC "\n",
                                          1,
                                          NULL,
                                          -1,
                                          0,
                                          NULL,
                                          NULL,
                                          &cap_fd);

    check_condition("spawn --mnemonic-stdin happy-path child", child > 0);
    if (child <= 0) {
        return;
    }

    fd = connect_socket_retry("nsigner_test_mi_stdin_ok", 5000);
    check_condition("--mnemonic-stdin reaches running server", fd >= 0);
    if (fd >= 0) {
        close(fd);
    }

    (void)kill(child, SIGTERM);
    (void)waitpid(child, &status, 0);
    if (cap_fd >= 0) {
        close(cap_fd);
    }
}

static void test_stdin_eof_without_newline(void) {
    const char *const argv[] = {
        "./build/nsigner", "--socket-name", "nsigner_test_mi_stdin_eof", "--mnemonic-stdin", NULL
    };
    int cap_fd = -1;
    int status = 0;
    int fd;
    pid_t child = spawn_with_stdin_and_fd(argv,
                                          VALID_MNEMONIC,
                                          1,
                                          NULL,
                                          -1,
                                          0,
                                          NULL,
                                          NULL,
                                          &cap_fd);

    check_condition("spawn --mnemonic-stdin EOF-without-newline child", child > 0);
    if (child <= 0) {
        return;
    }

    fd = connect_socket_retry("nsigner_test_mi_stdin_eof", 5000);
    check_condition("--mnemonic-stdin EOF without newline accepted", fd >= 0);
    if (fd >= 0) {
        close(fd);
    }

    (void)kill(child, SIGTERM);
    (void)waitpid(child, &status, 0);
    if (cap_fd >= 0) {
        close(cap_fd);
    }
}

static void test_stdin_invalid_mnemonic(void) {
    const char *const argv[] = {
        "./build/nsigner", "--mnemonic-stdin", NULL
    };
    int cap_fd = -1;
    int status = 0;
    char cap[512];
    pid_t child = spawn_with_stdin_and_fd(argv,
                                          "not a valid mnemonic\n",
                                          1,
                                          NULL,
                                          -1,
                                          0,
                                          NULL,
                                          NULL,
                                          &cap_fd);

    check_condition("spawn --mnemonic-stdin invalid child", child > 0);
    if (child <= 0) {
        return;
    }

    check_condition("--mnemonic-stdin invalid exits", wait_exit_timeout(child, 2000, &status) == 0);
    check_condition("--mnemonic-stdin invalid exits non-zero", WIFEXITED(status) && WEXITSTATUS(status) != 0);

    if (cap_fd >= 0) {
        (void)read_capture_nonblocking(cap_fd, cap, sizeof(cap));
        check_condition("--mnemonic-stdin invalid emits validation failure", strstr(cap, "mnemonic validation failed") != NULL);
        close(cap_fd);
    }
}

static void test_stdin_overflow(void) {
    const char *const argv[] = {
        "./build/nsigner", "--mnemonic-stdin", NULL
    };
    char long_input[LONG_INPUT_LEN + 2];
    int i;
    int cap_fd = -1;
    int status = 0;
    char cap[512];
    pid_t child;

    for (i = 0; i < LONG_INPUT_LEN; ++i) {
        long_input[i] = 'a';
    }
    long_input[LONG_INPUT_LEN] = '\n';
    long_input[LONG_INPUT_LEN + 1] = '\0';

    child = spawn_with_stdin_and_fd(argv,
                                    long_input,
                                    1,
                                    NULL,
                                    -1,
                                    0,
                                    NULL,
                                    NULL,
                                    &cap_fd);

    check_condition("spawn --mnemonic-stdin overflow child", child > 0);
    if (child <= 0) {
        return;
    }

    check_condition("--mnemonic-stdin overflow exits", wait_exit_timeout(child, 2000, &status) == 0);
    check_condition("--mnemonic-stdin overflow exits non-zero", WIFEXITED(status) && WEXITSTATUS(status) != 0);

    if (cap_fd >= 0) {
        (void)read_capture_nonblocking(cap_fd, cap, sizeof(cap));
        check_condition("--mnemonic-stdin overflow emits length error", strstr(cap, "exceeded maximum length") != NULL);
        close(cap_fd);
    }
}

static void test_fd_happy_path(void) {
    const char *const argv[] = {
        "./build/nsigner", "--socket-name", "nsigner_test_mi_fd_ok", "--mnemonic-fd", "3", NULL
    };
    int cap_fd = -1;
    int status = 0;
    int fd;
    pid_t child = spawn_with_stdin_and_fd(argv,
                                          "",
                                          0,
                                          VALID_MNEMONIC "\n",
                                          3,
                                          1,
                                          NULL,
                                          NULL,
                                          &cap_fd);

    check_condition("spawn --mnemonic-fd happy-path child", child > 0);
    if (child <= 0) {
        return;
    }

    fd = connect_socket_retry("nsigner_test_mi_fd_ok", 5000);
    check_condition("--mnemonic-fd reaches running server", fd >= 0);
    if (fd >= 0) {
        close(fd);
    }

    (void)kill(child, SIGTERM);
    (void)waitpid(child, &status, 0);
    if (cap_fd >= 0) {
        close(cap_fd);
    }
}

static void test_mutual_exclusion_and_validation_errors(void) {
    struct {
        const char *name;
        const char *const *argv;
        const char *expect_substr;
    } cases[] = {
        {
            "mutual exclusion stdin+fd",
            (const char *const[]){"./build/nsigner", "--mnemonic-stdin", "--mnemonic-fd", "3", NULL},
            "mutually exclusive"
        },
        {
            "stdin with stdio listener",
            (const char *const[]){"./build/nsigner", "--listen", "stdio", "--mnemonic-stdin", NULL},
            "requires --listen unix or --listen tcp"
        },
        {
            "mnemonic-fd 1 rejected",
            (const char *const[]){"./build/nsigner", "--mnemonic-fd", "1", NULL},
            "--mnemonic-fd 1 is not allowed"
        },
        {
            "mnemonic-fd 0 rejected with stdio",
            (const char *const[]){"./build/nsigner", "--listen", "stdio", "--mnemonic-fd", "0", NULL},
            "cannot be used with --listen stdio"
        }
    };

    size_t i;
    for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) {
        int cap_fd = -1;
        int status = 0;
        char cap[512];
        pid_t child = spawn_with_stdin_and_fd(cases[i].argv,
                                              "",
                                              1,
                                              NULL,
                                              -1,
                                              0,
                                              NULL,
                                              NULL,
                                              &cap_fd);

        check_condition(cases[i].name, child > 0);
        if (child <= 0) {
            continue;
        }

        check_condition("expected parse-time failure exits", wait_exit_timeout(child, 2000, &status) == 0);
        check_condition("expected parse-time failure non-zero", WIFEXITED(status) && WEXITSTATUS(status) != 0);

        if (cap_fd >= 0) {
            (void)read_capture_nonblocking(cap_fd, cap, sizeof(cap));
            check_condition("expected parse-time error message", strstr(cap, cases[i].expect_substr) != NULL);
            close(cap_fd);
        }
    }
}

static void test_lock_reunlock_falls_back_to_tui_after_fd_startup(void) {
    const char *const argv[] = {
        "./build/nsigner", "--socket-name", "nsigner_test_mi_lock", "--mnemonic-fd", "3", NULL
    };
    int stdin_write = -1;
    int cap_fd = -1;
    int status = 0;
    int fd;
    pid_t child = spawn_with_stdin_and_fd(argv,
                                          "",
                                          0,
                                          VALID_MNEMONIC "\n",
                                          3,
                                          1,
                                          &stdin_write,
                                          NULL,
                                          &cap_fd);

    check_condition("spawn --mnemonic-fd lock test child", child > 0);
    if (child <= 0) {
        return;
    }

    fd = connect_socket_retry("nsigner_test_mi_lock", 5000);
    check_condition("--mnemonic-fd lock test initial running server", fd >= 0);
    if (fd >= 0) {
        close(fd);
    }

    (void)write_full(stdin_write, "l\n" VALID_MNEMONIC "\n", strlen("l\n" VALID_MNEMONIC "\n"));
    sleep_ms(800);

    fd = connect_socket_retry("nsigner_test_mi_lock", 2000);
    check_condition("lock+reunlock via TUI keeps server alive", fd >= 0);
    if (fd >= 0) {
        close(fd);
    }

    if (stdin_write >= 0) {
        close(stdin_write);
    }

    (void)kill(child, SIGTERM);
    (void)waitpid(child, &status, 0);
    if (cap_fd >= 0) {
        close(cap_fd);
    }
}

int main(void) {
    (void)signal(SIGPIPE, SIG_IGN);

    test_stdin_happy_path();
    test_stdin_eof_without_newline();
    test_stdin_invalid_mnemonic();
    test_stdin_overflow();
    test_fd_happy_path();
    test_mutual_exclusion_and_validation_errors();
    test_lock_reunlock_falls_back_to_tui_after_fd_startup();

    if (g_failures == 0) {
        printf("ALL TESTS PASSED\n");
        return 0;
    }

    printf("TESTS FAILED: %d\n", g_failures);
    return 1;
}
