#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <stdbool.h>
#include <ctype.h>

#include "lftpd.h"
#include "private/lftpd_status.h"
#include "private/lftpd_inet.h"
#include "private/lftpd_log.h"
#include "private/lftpd_string.h"
#include "private/lftpd_io.h"
#include "net_socket.h"
#include "net_config.h"
#include "ff.h"

typedef struct {
    const char *command;
    int (*handler)(lftpd_client_t *client, const char *arg);
} command_t;

static int cmd_cwd(lftpd_client_t *client, const char *arg);
static int cmd_dele(lftpd_client_t *client, const char *arg);
static int cmd_epsv(lftpd_client_t *client, const char *arg);
static int cmd_feat(lftpd_client_t *client, const char *arg);
static int cmd_list(lftpd_client_t *client, const char *arg);
static int cmd_nlst(lftpd_client_t *client, const char *arg);
static int cmd_noop(lftpd_client_t *client, const char *arg);
static int cmd_pass(lftpd_client_t *client, const char *arg);
static int cmd_pasv(lftpd_client_t *client, const char *arg);
static int cmd_pwd(lftpd_client_t *client, const char *arg);
static int cmd_quit(lftpd_client_t *client, const char *arg);
static int cmd_retr(lftpd_client_t *client, const char *arg);
static int cmd_size(lftpd_client_t *client, const char *arg);
static int cmd_stor(lftpd_client_t *client, const char *arg);
static int cmd_syst(lftpd_client_t *client, const char *arg);
static int cmd_type(lftpd_client_t *client, const char *arg);
static int cmd_user(lftpd_client_t *client, const char *arg);

static command_t commands[] = {
    { "CWD",  cmd_cwd  },
    { "DELE", cmd_dele },
    { "EPSV", cmd_epsv },
    { "FEAT", cmd_feat },
    { "LIST", cmd_list },
    { "NLST", cmd_nlst },
    { "NOOP", cmd_noop },
    { "PASS", cmd_pass },
    { "PASV", cmd_pasv },
    { "PWD",  cmd_pwd  },
    { "QUIT", cmd_quit },
    { "RETR", cmd_retr },
    { "SIZE", cmd_size },
    { "STOR", cmd_stor },
    { "SYST", cmd_syst },
    { "TYPE", cmd_type },
    { "USER", cmd_user },
    { NULL,   NULL     },
};

static int send_response(int socket, int code, bool include_code,
                         bool multiline_start, const char *format, ...)
{
    va_list args;
    char message[256];
    char response[512];

    va_start(args, format);
    vsnprintf(message, sizeof(message), format, args);
    va_end(args);

    if (include_code) {
        if (multiline_start) {
            snprintf(response, sizeof(response), "%d-%s%s", code, message, CRLF);
        } else {
            snprintf(response, sizeof(response), "%d %s%s", code, message, CRLF);
        }
    } else {
        snprintf(response, sizeof(response), "%s%s", message, CRLF);
    }

    return lftpd_inet_write_string(socket, response);
}

#define send_simple_response(socket, code, format, ...) \
    send_response(socket, code, true, false, format, ##__VA_ARGS__)

#define send_multiline_response_begin(socket, code, format, ...) \
    send_response(socket, code, true, true, format, ##__VA_ARGS__)

#define send_multiline_response_line(socket, format, ...) \
    send_response(socket, 0, false, false, format, ##__VA_ARGS__)

#define send_multiline_response_end(socket, code, format, ...) \
    send_response(socket, code, true, false, format, ##__VA_ARGS__)

static int send_list(int socket, const char *path)
{
    static const char *directory_format = "drw-rw-rw- 1 owner group %13lu Jan 01  1970 %s";
    static const char *file_format     = "-rw-rw-rw- 1 owner group %13lu Jan 01  1970 %s";

    void *dp = lftpd_io_opendir(path);
    if (dp == NULL) {
        return -1;
    }

    char name[256];
    uint32_t size;
    int is_dir;

    while (lftpd_io_readdir(dp, name, sizeof(name), &size, &is_dir) == 0) {
        if (is_dir) {
            send_multiline_response_line(socket, directory_format,
                                         (unsigned long)size, name);
        } else {
            send_multiline_response_line(socket, file_format,
                                         (unsigned long)size, name);
        }
    }

    lftpd_io_closedir(dp);
    return 0;
}

static int send_nlst(int socket, const char *path)
{
    void *dp = lftpd_io_opendir(path);
    if (dp == NULL) {
        return -1;
    }

    char name[256];
    uint32_t size;
    int is_dir;

    while (lftpd_io_readdir(dp, name, sizeof(name), &size, &is_dir) == 0) {
        if (!is_dir) {
            send_multiline_response_line(socket, name);
        }
    }

    lftpd_io_closedir(dp);
    return 0;
}

static int send_file(int socket, const char *path)
{
    int fh = lftpd_io_open_read(path);
    if (fh < 0) {
        lftpd_log_error("failed to open file for read");
        return -1;
    }

    unsigned char buffer[1024];
    int read_len;

    while ((read_len = lftpd_io_read(fh, buffer, sizeof(buffer))) > 0) {
        unsigned char *p = buffer;
        while (read_len > 0) {
            int write_len = lftpd_inet_write(socket, p, read_len);
            if (write_len < 0) {
                lftpd_log_error("write error");
                lftpd_io_close(fh);
                return -1;
            }
            p += write_len;
            read_len -= write_len;
        }
    }

    lftpd_io_close(fh);
    return 0;
}

static int receive_file(int socket, const char *path)
{
    int fh = lftpd_io_open_write(path);
    if (fh < 0) {
        lftpd_log_error("failed to open file for write");
        return -1;
    }

    unsigned char buffer[1024];
    int err;
    int total = 0;

    while ((err = lftpd_inet_read(socket, buffer, sizeof(buffer))) > 0) {
        total += err;
        if (lftpd_io_write(fh, buffer, err) != err) {
            err = -1;
            break;
        }
    }

    lftpd_io_close(fh);

    if (err < 0) {
        return err;
    }
    if (total == 0) {
        lftpd_log_error("received 0 bytes");
        lftpd_io_unlink(path);
        return -1;
    }
    return 0;
}

static int cmd_cwd(lftpd_client_t *client, const char *arg)
{
    if (arg == NULL || strlen(arg) == 0) {
        send_simple_response(client->socket, 550, STATUS_550);
        return -1;
    }

    char *path = lftpd_io_canonicalize_path(client->directory, arg);

    /* 根目录始终有效，跳过 stat 检查（FatFS FF_FS_RPATH=0 下 stat 根目录可能失败） */
    if (strcmp(path, "/") != 0) {
        uint32_t size;
        int is_dir;
        if (lftpd_io_stat(path, &size, &is_dir) != 0 || !is_dir) {
            send_simple_response(client->socket, 550, STATUS_550);
            free(path);
            return -1;
        }
    }

    strncpy(client->directory, path, sizeof(client->directory) - 1);
    client->directory[sizeof(client->directory) - 1] = '\0';
    free(path);

    send_simple_response(client->socket, 250, STATUS_250);
    return 0;
}

static int cmd_dele(lftpd_client_t *client, const char *arg)
{
    if (arg == NULL || strlen(arg) == 0) {
        send_simple_response(client->socket, 550, STATUS_550);
        return -1;
    }

    char *path = lftpd_io_canonicalize_path(client->directory, arg);

    uint32_t size;
    int is_dir;
    if (lftpd_io_stat(path, &size, &is_dir) != 0) {
        send_simple_response(client->socket, 550, STATUS_550);
        free(path);
        return -1;
    }

    if (is_dir) {
        send_simple_response(client->socket, 550, STATUS_550);
        free(path);
        return -1;
    }

    lftpd_io_unlink(path);
    free(path);
    send_simple_response(client->socket, 250, STATUS_250);
    return 0;
}

static int cmd_epsv(lftpd_client_t *client, const char *arg)
{
    if (client->data_socket >= 0) {
        lftpd_inet_close(client->data_socket);
        client->data_socket = -1;
    }

    int listener_socket = lftpd_inet_listen(0);
    if (listener_socket < 0) {
        send_simple_response(client->socket, 425, STATUS_425);
        return -1;
    }

    int port = lftpd_inet_get_socket_port(listener_socket);

    send_simple_response(client->socket, 229, STATUS_229, port);

    lftpd_log_debug("EPSV listener on port %d", port);

    client->data_socket = listener_socket;
    return 0;
}

static int cmd_feat(lftpd_client_t *client, const char *arg)
{
    send_multiline_response_begin(client->socket, 211, STATUS_211);
    send_multiline_response_line(client->socket, "EPSV");
    send_multiline_response_line(client->socket, "PASV");
    send_multiline_response_line(client->socket, "SIZE");
    send_multiline_response_line(client->socket, "NLST");
    send_multiline_response_end(client->socket, 211, STATUS_211);
    return 0;
}

static int cmd_list(lftpd_client_t *client, const char *arg)
{
    if (client->data_socket == -1) {
        send_simple_response(client->socket, 425, STATUS_425);
        return -1;
    }

    send_simple_response(client->socket, 150, STATUS_150);
    int data_sock = lftpd_inet_accept_timeout(client->data_socket, 5000);
    if (data_sock < 0) {
        lftpd_log_error("%s accept failed, sk=%d", __func__, client->data_socket);
        send_simple_response(client->socket, 425, STATUS_425);
        lftpd_inet_close(client->data_socket);
        client->data_socket = -1;
        return -1;
    }

    int err = send_list(data_sock, client->directory);
    lftpd_inet_close(data_sock);
    client->data_socket = -1;

    if (err == 0) {
        send_simple_response(client->socket, 226, STATUS_226);
    } else {
        send_simple_response(client->socket, 550, STATUS_550);
    }
    return 0;
}

static int cmd_nlst(lftpd_client_t *client, const char *arg)
{
    if (client->data_socket == -1) {
        send_simple_response(client->socket, 425, STATUS_425);
        return -1;
    }

    send_simple_response(client->socket, 150, STATUS_150);
    int data_sock = lftpd_inet_accept_timeout(client->data_socket, 5000);
    if (data_sock < 0) {
        lftpd_log_error("%s accept failed, sk=%d", __func__, client->data_socket);
        send_simple_response(client->socket, 425, STATUS_425);
        lftpd_inet_close(client->data_socket);
        client->data_socket = -1;
        return -1;
    }

    int err = send_nlst(data_sock, client->directory);
    lftpd_inet_close(data_sock);
    client->data_socket = -1;

    if (err == 0) {
        send_simple_response(client->socket, 226, STATUS_226);
    } else {
        send_simple_response(client->socket, 550, STATUS_550);
    }
    return 0;
}

static int cmd_noop(lftpd_client_t *client, const char *arg)
{
    send_simple_response(client->socket, 200, STATUS_200);
    return 0;
}

static int cmd_pass(lftpd_client_t *client, const char *arg)
{
    send_simple_response(client->socket, 230, STATUS_230);
    return 0;
}

static int cmd_pasv(lftpd_client_t *client, const char *arg)
{
    int listener_socket = -1;
    int retry;

    if (client->data_socket >= 0) {
        lftpd_inet_close(client->data_socket);
        client->data_socket = -1;
    }

    for (retry = 0; retry < 3; retry++) {
        listener_socket = lftpd_inet_listen(0);
        if (listener_socket >= 0) break;
        vTaskDelay(pdMS_TO_TICKS(200));
    }
    if (listener_socket < 0) {
        send_simple_response(client->socket, 425, STATUS_425);
        return -1;
    }

    int port = lftpd_inet_get_socket_port(listener_socket);

    {
        struct net_sockaddr_in local_addr;
        int addrlen = sizeof(local_addr);
        int err = net_getsockname(client->socket,
                                  (struct net_sockaddr *)&local_addr, &addrlen);
        if (err != 0) {
            lftpd_log_error("error getting client IP info");
            send_simple_response(client->socket, 425, STATUS_425);
            lftpd_inet_close(listener_socket);
            return -1;
        }

        uint32_t ip = net_htonl(local_addr.sin_addr.s_addr);
        send_simple_response(client->socket, 227, STATUS_227,
                             (ip >> 24) & 0xff,
                             (ip >> 16) & 0xff,
                             (ip >> 8) & 0xff,
                             (ip >> 0) & 0xff,
                             (port >> 8) & 0xff, (port >> 0) & 0xff);
    }

    lftpd_log_debug("PASV listener on port %d", port);

    client->data_socket = listener_socket;
    return 0;
}

static int cmd_pwd(lftpd_client_t *client, const char *arg)
{
    send_simple_response(client->socket, 257, "\"%s\"", client->directory);
    return 0;
}

static int cmd_quit(lftpd_client_t *client, const char *arg)
{
    send_simple_response(client->socket, 221, STATUS_221);
    return -1;
}

static int cmd_retr(lftpd_client_t *client, const char *arg)
{
    if (client->data_socket == -1) {
        send_simple_response(client->socket, 425, STATUS_425);
        return -1;
    }

    send_simple_response(client->socket, 150, STATUS_150);
    int data_sock = lftpd_inet_accept_timeout(client->data_socket, 5000);
    if (data_sock < 0) {
        lftpd_log_error("%s accept failed, sk=%d", __func__, client->data_socket);
        send_simple_response(client->socket, 425, STATUS_425);
        lftpd_inet_close(client->data_socket);
        client->data_socket = -1;
        return -1;
    }

    char *path = lftpd_io_canonicalize_path(client->directory, arg);
    lftpd_log_debug("send '%s'", path);
    int err = send_file(data_sock, path);
    lftpd_inet_close(data_sock);
    client->data_socket = -1;

    if (err == 0) {
        lftpd_log_info("DOWNLOAD '%s' OK", path);
        send_simple_response(client->socket, 226, STATUS_226);
    } else {
        lftpd_log_error("DOWNLOAD '%s' FAILED", path);
        send_simple_response(client->socket, 450, STATUS_450);
    }
    free(path);
    return 0;
}

static int cmd_size(lftpd_client_t *client, const char *arg)
{
    if (arg == NULL) {
        send_simple_response(client->socket, 550, STATUS_550);
        return 0;
    }

    char *path = lftpd_io_canonicalize_path(client->directory, arg);
    lftpd_log_debug("size %s", path);

    uint32_t size;
    int is_dir;
    if (lftpd_io_stat(path, &size, &is_dir) == 0 && !is_dir) {
        send_simple_response(client->socket, 213, "%lu", (unsigned long)size);
    } else {
        send_simple_response(client->socket, 550, STATUS_550);
    }

    free(path);
    return 0;
}

static int cmd_stor(lftpd_client_t *client, const char *arg)
{
    if (client->data_socket == -1) {
        send_simple_response(client->socket, 425, STATUS_425);
        return -1;
    }

    send_simple_response(client->socket, 150, STATUS_150);
    int data_sock = lftpd_inet_accept_timeout(client->data_socket, 5000);
    if (data_sock < 0) {
        lftpd_log_error("%s accept failed, sk=%d", __func__, client->data_socket);
        send_simple_response(client->socket, 425, STATUS_425);
        lftpd_inet_close(client->data_socket);
        client->data_socket = -1;
        return -1;
    }

    char *path = lftpd_io_canonicalize_path(client->directory, arg);
    lftpd_log_debug("receive '%s'", path);
    int err = receive_file(data_sock, path);
    lftpd_inet_close(data_sock);
    client->data_socket = -1;

    if (err == 0) {
        uint32_t size = 0;
        int is_dir = 0;
        lftpd_io_stat(path, &size, &is_dir);
        lftpd_log_info("UPLOAD '%s' %lu bytes OK", path, (unsigned long)size);
        send_simple_response(client->socket, 226, STATUS_226);
    } else {
        lftpd_log_error("UPLOAD '%s' FAILED", path);
        send_simple_response(client->socket, 450, STATUS_450);
    }
    free(path);
    return 0;
}

static int cmd_syst(lftpd_client_t *client, const char *arg)
{
    send_simple_response(client->socket, 215, "UNIX Type: L8");
    return 0;
}

static int cmd_type(lftpd_client_t *client, const char *arg)
{
    send_simple_response(client->socket, 200, STATUS_200);
    return 0;
}

static int cmd_user(lftpd_client_t *client, const char *arg)
{
    send_simple_response(client->socket, 230, STATUS_230);
    return 0;
}

static int handle_control_channel(lftpd_client_t *client)
{
    int err = send_simple_response(client->socket, 220, STATUS_220);
    if (err != 0) {
        lftpd_log_error("error sending welcome message");
        goto cleanup;
    }

    char read_buffer[512];

    while (err == 0) {
        int line_len = lftpd_inet_read_line(client->socket, read_buffer,
                                            sizeof(read_buffer));
        if (line_len != 0) {
            lftpd_log_error("error reading next command");
            goto cleanup;
        }

        int index;
        char *p = strchr(read_buffer, ' ');
        if (p != NULL) {
            index = (int)(p - read_buffer);
        } else {
            index = (int)strlen(read_buffer);
        }

        if (index >= 5) {
            err = send_simple_response(client->socket, 500, STATUS_500);
            continue;
        }

        char command_tmp[4 + 1];
        memset(command_tmp, 0, sizeof(command_tmp));
        memcpy(command_tmp, read_buffer, (size_t)index);

        for (int i = 0; command_tmp[i]; i++) {
            command_tmp[i] = (char)toupper((int)command_tmp[i]);
        }

        bool matched = false;
        for (int i = 0; commands[i].command; i++) {
            if (strcmp(commands[i].command, command_tmp) == 0) {
                char arg_buf[512];
                const char *arg = NULL;

                if (index < (int)strlen(read_buffer)) {
                    strncpy(arg_buf, read_buffer + index + 1, sizeof(arg_buf) - 1);
                    arg_buf[sizeof(arg_buf) - 1] = '\0';
                    arg = lftpd_string_trim(arg_buf);
                }

                lftpd_log_info("CMD %s %s", command_tmp, arg ? arg : "");
                err = commands[i].handler(client, arg);
                matched = true;
                break;
            }
        }

        if (!matched) {
            send_simple_response(client->socket, 502, STATUS_502);
        }
    }

cleanup:
    lftpd_inet_close(client->socket);
    return 0;
}

int lftpd_start(const char *directory, int port, lftpd_t *lftpd)
{
    memset(lftpd, 0, sizeof(lftpd_t));

    lftpd->directory = directory;
    lftpd->port = port;

    while (true) {
        lftpd->server_socket = lftpd_inet_listen(port);
        if (lftpd->server_socket < 0) {
            lftpd_log_error("error creating listener");
            vTaskDelay(pdMS_TO_TICKS(3000));
            continue;
        }

        lftpd_log_info("waiting for connection...");

        int client_socket = lftpd_inet_accept(lftpd->server_socket);
        if (client_socket < 0) {
            lftpd_log_error("error accepting client socket");
            lftpd_inet_close(lftpd->server_socket);
            continue;
        }

        {
            char ip_str[16];
            struct net_sockaddr_in addr;
            int addrlen = sizeof(addr);
            if (net_getsockname(client_socket,
                                (struct net_sockaddr *)&addr, &addrlen) == 0) {
                net_inet_ntoa(addr.sin_addr.s_addr, ip_str);
                int remote_port = lftpd_inet_get_socket_port(client_socket);
                lftpd_log_info("connection received from %s:%d...",
                               ip_str, remote_port);
            } else {
                lftpd_log_info("connection received...");
            }
        }

        lftpd_client_t client;
        memset(&client, 0, sizeof(client));
        strncpy(client.directory, directory, sizeof(client.directory) - 1);
        client.directory[sizeof(client.directory) - 1] = '\0';
        client.socket = client_socket;
        client.data_socket = -1;

        lftpd->client = &client;
        handle_control_channel(&client);
        lftpd->client = NULL;

        /* standalone mode: socket was closed in handle_control_channel,
         * loop back to create a new listener */
    }
}

int lftpd_stop(lftpd_t *lftpd)
{
    if (lftpd->server_socket >= 0) {
        lftpd_inet_close(lftpd->server_socket);
    }
    if (lftpd->client) {
        lftpd_inet_close(lftpd->client->socket);
    }
    return 0;
}
