#include "ftp_manager.h"
#include "esp_log.h"

#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include <string.h>
#include <stdio.h>

static const char *TAG = "FTP_MANAGER";

/* Receive one line terminated by \r\n into buffer (null-terminated) */
static int ftp_recv_line(int sock, char *buf, size_t len)
{
    size_t pos = 0;
    while (pos < len - 1)
    {
        char c;
        int r = recv(sock, &c, 1, 0);
        if (r <= 0) {
            break;
        }
        buf[pos++] = c;
        if (pos >= 2 && buf[pos-2] == '\r' && buf[pos-1] == '\n') {
            break;
        }
    }
    buf[pos] = '\0';
    return (int)pos;
}

/* Send a command followed by \r\n */
static int ftp_send_cmd(int sock, const char *cmd)
{
    char line[256];
    int len = snprintf(line, sizeof(line), "%s\r\n", cmd);
    if (len <= 0) return -1;
    return send(sock, line, len, 0);
}

/* Resolve hostname or dotted IP and connect */
static int ftp_connect_control(const char *host, int port)
{
    char port_str[8];
    snprintf(port_str, sizeof(port_str), "%d", port);

    struct addrinfo hints = {0};
    hints.ai_family   = AF_INET;
    hints.ai_socktype = SOCK_STREAM;

    struct addrinfo *res = NULL;
    int err = getaddrinfo(host, port_str, &hints, &res);
    if (err != 0 || res == NULL) {
        ESP_LOGE(TAG, "getaddrinfo failed for host '%s': %d", host, err);
        return -1;
    }

    int sock = -1;
    struct addrinfo *p;
    for (p = res; p != NULL; p = p->ai_next) {
        sock = socket(p->ai_family, p->ai_socktype, 0);
        if (sock < 0) continue;

        if (connect(sock, p->ai_addr, p->ai_addrlen) == 0) {
            break;  // success
        }

        ESP_LOGW(TAG, "connect() failed to one addr, retrying next");
        close(sock);
        sock = -1;
    }

    freeaddrinfo(res);

    if (sock < 0) {
        ESP_LOGE(TAG, "Could not connect to FTP server %s:%d", host, port);
        return -1;
    }

    ESP_LOGI(TAG, "Connected to FTP server %s:%d", host, port);
    return sock;
}

ftp_err_t ftp_manager_download(const ftp_config_t *cfg,
                               ftp_progress_cb_t cb)
{
    if (!cfg || !cfg->server || !cfg->remote_path || !cfg->local_path) {
        ESP_LOGE(TAG, "Invalid FTP config");
        return FTP_ERR_CONNECT;
    }

    ESP_LOGI(TAG, "FTP download start: server=%s port=%d remote=%s local=%s",
             cfg->server, cfg->port, cfg->remote_path, cfg->local_path);

    int ctrl_sock = ftp_connect_control(cfg->server, cfg->port);
    if (ctrl_sock < 0) {
        return FTP_ERR_CONNECT;
    }

    char line[256];

    /* Read welcome line */
    ftp_recv_line(ctrl_sock, line, sizeof(line));
    ESP_LOGI(TAG, "FTP: %s", line);

    /* USER */
    char cmd[128];
    snprintf(cmd, sizeof(cmd), "USER %s", cfg->username ? cfg->username : "anonymous");
    ftp_send_cmd(ctrl_sock, cmd);
    ftp_recv_line(ctrl_sock, line, sizeof(line));
    ESP_LOGI(TAG, "FTP: %s", line);

    /* PASS */
    snprintf(cmd, sizeof(cmd), "PASS %s", cfg->password ? cfg->password : "anonymous");
    ftp_send_cmd(ctrl_sock, cmd);
    ftp_recv_line(ctrl_sock, line, sizeof(line));
    ESP_LOGI(TAG, "FTP: %s", line);

    /* VERY SIMPLE IMPLEMENTATION: use passive mode for data connection */

    /* PASV */
    ftp_send_cmd(ctrl_sock, "PASV");
    ftp_recv_line(ctrl_sock, line, sizeof(line));
    ESP_LOGI(TAG, "FTP: %s", line);

    /* Parse "(h1,h2,h3,h4,p1,p2)" from 227 response */
    int h1,h2,h3,h4,p1,p2;
    char *p = strchr(line, '(');
    if (!p || sscanf(p+1, "%d,%d,%d,%d,%d,%d",
                     &h1,&h2,&h3,&h4,&p1,&p2) != 6) {
        ESP_LOGE(TAG, "Failed to parse PASV response");
        close(ctrl_sock);
        return FTP_ERR_DOWNLOAD;
    }

    char data_host[32];
    snprintf(data_host, sizeof(data_host), "%d.%d.%d.%d", h1,h2,h3,h4);
    int data_port = p1*256 + p2;
    ESP_LOGI(TAG, "FTP data connection to %s:%d", data_host, data_port);

    int data_sock = ftp_connect_control(data_host, data_port);
    if (data_sock < 0) {
        close(ctrl_sock);
        return FTP_ERR_CONNECT;
    }

    /* RETR */
    snprintf(cmd, sizeof(cmd), "RETR %s", cfg->remote_path);
    ftp_send_cmd(ctrl_sock, cmd);
    ftp_recv_line(ctrl_sock, line, sizeof(line));
    ESP_LOGI(TAG, "FTP: %s", line);

    /* Now receive file data on data_sock */
    FILE *fp = fopen(cfg->local_path, "wb");
    if (!fp) {
        ESP_LOGE(TAG, "Failed to open local file %s", cfg->local_path);
        close(data_sock);
        close(ctrl_sock);
        return FTP_ERR_FILE;
    }

    uint8_t buf[2048];
    size_t total = 0;

    while (1) {
        int r = recv(data_sock, buf, sizeof(buf), 0);
        if (r < 0) {
            ESP_LOGE(TAG, "Error reading data socket");
            fclose(fp);
            close(data_sock);
            close(ctrl_sock);
            return FTP_ERR_DOWNLOAD;
        } else if (r == 0) {
            break; // done
        }
        fwrite(buf, 1, r, fp);
        total += r;

        if (cb) cb(total, 0);
    }

    fclose(fp);
    close(data_sock);

    /* Read final transfer response */
    ftp_recv_line(ctrl_sock, line, sizeof(line));
    ESP_LOGI(TAG, "FTP: %s", line);

    /* QUIT */
    ftp_send_cmd(ctrl_sock, "QUIT");
    ftp_recv_line(ctrl_sock, line, sizeof(line));
    ESP_LOGI(TAG, "FTP: %s", line);

    close(ctrl_sock);

    ESP_LOGI(TAG, "FTP download complete, total bytes=%u", (unsigned)total);
    return FTP_OK;
}
