#include "WebServer.hpp"
#include "../SdCard.hpp"
#include "esp_log.h"
#include <cstring>
#include <string.h>
#include <cstdio>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>

// Helper for min
template<typename T>
static inline T minVal(T a, T b) { return (a < b) ? a : b; }

static const char* TAG = "WebServer";

// Embedded file manager HTML (from Flash)
extern const uint8_t filemanager_html_start[] asm("_binary_filemanager_html_start");
extern const uint8_t filemanager_html_end[] asm("_binary_filemanager_html_end");

// Maximum upload size (16 MB) - streaming, so RAM usage is low
static constexpr size_t MAX_UPLOAD_SIZE = 16 * 1024 * 1024;
// Buffer size for streaming uploads
static constexpr size_t UPLOAD_BUFFER_SIZE = 4096;

// GET /files - Serve embedded file manager HTML
esp_err_t WebServer::fileManagerHandler(httpd_req_t* req) {
    httpd_resp_set_type(req, "text/html; charset=utf-8");
    size_t len = filemanager_html_end - filemanager_html_start;
    httpd_resp_send(req, (const char*)filemanager_html_start, len);
    return ESP_OK;
}

// GET /api/files/list?path=/ - List directory contents
esp_err_t WebServer::filesListHandler(httpd_req_t* req) {
    char path[128] = "/";
    getQueryParam(req, "path", path, sizeof(path));

    char fullPath[256];
    snprintf(fullPath, sizeof(fullPath), "%.7s%.127s", SdCard::MOUNT_POINT, path);

    if (!SdCard::instance().isMounted()) {
        return sendJsonError(req, "SD card not mounted");
    }

    DIR* dir = opendir(fullPath);
    if (!dir) {
        return sendJsonError(req, "Cannot open directory");
    }

    cJSON* json = cJSON_CreateObject();
    cJSON_AddBoolToObject(json, "success", true);
    cJSON_AddStringToObject(json, "path", path);
    cJSON* files = cJSON_AddArrayToObject(json, "files");

    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;

        char entryPath[512];
        size_t fullPathLen = strlen(fullPath);
        const char* sep = (fullPathLen > 0 && fullPath[fullPathLen-1] == '/') ? "" : "/";
        snprintf(entryPath, sizeof(entryPath), "%.255s%s%.255s", fullPath, sep, entry->d_name);

        struct stat st;
        bool isDir = false;
        size_t fileSize = 0;

        if (stat(entryPath, &st) == 0) {
            isDir = S_ISDIR(st.st_mode);
            fileSize = st.st_size;
        } else {
            isDir = (entry->d_type == DT_DIR);
        }

        cJSON* fileObj = cJSON_CreateObject();
        cJSON_AddStringToObject(fileObj, "name", entry->d_name);
        cJSON_AddBoolToObject(fileObj, "isDir", isDir);
        cJSON_AddNumberToObject(fileObj, "size", (double)fileSize);
        cJSON_AddItemToArray(files, fileObj);
    }

    closedir(dir);
    return sendJsonObject(req, json);
}

// POST /api/files/upload?path=/ - Upload file (multipart/form-data) with streaming
esp_err_t WebServer::filesUploadHandler(httpd_req_t* req) {
    char path[128] = "/";
    getQueryParam(req, "path", path, sizeof(path));

    if (!SdCard::instance().isMounted()) {
        return sendJsonError(req, "SD card not mounted");
    }

    if (req->content_len > MAX_UPLOAD_SIZE) {
        return sendJsonError(req, "File too large (max 16MB)");
    }

    char contentType[128] = "";
    if (httpd_req_get_hdr_value_str(req, "Content-Type", contentType, sizeof(contentType)) != ESP_OK) {
        return sendJsonError(req, "Missing Content-Type");
    }

    char* boundaryStart = strstr(contentType, "boundary=");
    if (!boundaryStart) {
        return sendJsonError(req, "Invalid multipart boundary");
    }
    boundaryStart += 9;

    if (*boundaryStart == '"') boundaryStart++;

    char boundaryValue[72];
    strncpy(boundaryValue, boundaryStart, sizeof(boundaryValue) - 1);
    boundaryValue[sizeof(boundaryValue) - 1] = '\0';

    char* endPtr = boundaryValue;
    while (*endPtr && *endPtr != '"' && *endPtr != ';' && *endPtr != ' ' && *endPtr != '\r' && *endPtr != '\n') {
        endPtr++;
    }
    *endPtr = '\0';

    char boundary[80];
    snprintf(boundary, sizeof(boundary), "\r\n--%s", boundaryValue);
    size_t boundaryLen = strlen(boundary);

    // Allocate streaming buffer
    char* buffer = new char[UPLOAD_BUFFER_SIZE];
    if (!buffer) {
        return sendJsonError(req, "Out of memory");
    }

    size_t contentLen = req->content_len;
    size_t totalReceived = 0;
    size_t bufferFilled = 0;

    // Phase 1: Read header to find filename and data start
    // Headers are typically < 512 bytes, read up to 2KB to be safe
    static constexpr size_t HEADER_MAX = 2048;
    char header[HEADER_MAX];
    size_t headerLen = 0;
    char* dataStartInHeader = nullptr;

    while (headerLen < HEADER_MAX && totalReceived < contentLen) {
        int ret = httpd_req_recv(req, header + headerLen,
                                  minVal(HEADER_MAX - headerLen, contentLen - totalReceived));
        if (ret <= 0) {
            delete[] buffer;
            return sendJsonError(req, "Receive failed");
        }
        headerLen += ret;
        totalReceived += ret;

        // Look for end of headers (\r\n\r\n)
        dataStartInHeader = (char*)memmem(header, headerLen, "\r\n\r\n", 4);
        if (dataStartInHeader) {
            dataStartInHeader += 4; // Skip past \r\n\r\n
            break;
        }
    }

    if (!dataStartInHeader) {
        delete[] buffer;
        return sendJsonError(req, "Invalid multipart header");
    }

    // Extract filename from header
    char* filenameStart = strstr(header, "filename=\"");
    if (!filenameStart) {
        delete[] buffer;
        return sendJsonError(req, "No filename found");
    }
    filenameStart += 10;
    char* filenameEnd = strchr(filenameStart, '"');
    if (!filenameEnd || filenameEnd > dataStartInHeader) {
        delete[] buffer;
        return sendJsonError(req, "Invalid filename");
    }

    size_t filenameLen = filenameEnd - filenameStart;
    char filename[128];
    if (filenameLen >= sizeof(filename)) filenameLen = sizeof(filename) - 1;
    memcpy(filename, filenameStart, filenameLen);
    filename[filenameLen] = '\0';

    // Prepare target path
    char resolvedDir[384];
    char targetDirPath[256];
    snprintf(targetDirPath, sizeof(targetDirPath), "%.7s%.127s", SdCard::MOUNT_POINT, path);

    const char* actualDir = targetDirPath;
    if (resolveCaseInsensitivePath(targetDirPath, resolvedDir, sizeof(resolvedDir))) {
        actualDir = resolvedDir;
    }

    char fullPath[384];
    snprintf(fullPath, sizeof(fullPath), "%.250s/%.120s", actualDir, filename);

    ESP_LOGI(TAG, "Streaming upload: %s (total content: %zu bytes)", fullPath, contentLen);

    // Create directory if needed
    struct stat dirStat;
    if (stat(actualDir, &dirStat) != 0) {
        if (mkdir(actualDir, 0755) != 0 && errno != EEXIST) {
            delete[] buffer;
            return sendJsonError(req, "Cannot create directory");
        }
    }

    // Open file for writing
    FILE* f = fopen(fullPath, "wb");
    if (!f) {
        ESP_LOGE(TAG, "Cannot create file: %s (errno=%d)", fullPath, errno);
        delete[] buffer;
        return sendJsonError(req, "Cannot create file");
    }

    // Calculate how much file data is already in our header buffer
    size_t headerDataLen = headerLen - (dataStartInHeader - header);
    size_t fileSize = 0;

    // Copy initial file data from header buffer to streaming buffer
    memcpy(buffer, dataStartInHeader, headerDataLen);
    bufferFilled = headerDataLen;

    // Phase 2: Stream remaining data, looking for end boundary
    bool boundaryFound = false;

    while (totalReceived < contentLen || bufferFilled > 0) {
        // Try to receive more data if buffer has space
        if (totalReceived < contentLen && bufferFilled < UPLOAD_BUFFER_SIZE) {
            size_t toRead = minVal(UPLOAD_BUFFER_SIZE - bufferFilled, contentLen - totalReceived);
            int ret = httpd_req_recv(req, buffer + bufferFilled, toRead);
            if (ret < 0) {
                fclose(f);
                unlink(fullPath);
                delete[] buffer;
                return sendJsonError(req, "Receive failed");
            }
            if (ret > 0) {
                bufferFilled += ret;
                totalReceived += ret;
            }
        }

        // Check for boundary in buffer
        char* boundaryPos = nullptr;
        if (bufferFilled >= boundaryLen) {
            boundaryPos = (char*)memmem(buffer, bufferFilled, boundary, boundaryLen);
        }

        if (boundaryPos) {
            // Found end boundary - write data before it
            size_t dataToWrite = boundaryPos - buffer;
            if (dataToWrite > 0) {
                size_t written = fwrite(buffer, 1, dataToWrite, f);
                if (written != dataToWrite) {
                    fclose(f);
                    unlink(fullPath);
                    delete[] buffer;
                    return sendJsonError(req, "Write failed");
                }
                fileSize += written;
            }
            boundaryFound = true;
            break;
        }

        // No boundary found - write safe portion (keep boundaryLen-1 bytes for boundary detection)
        if (totalReceived >= contentLen) {
            // All data received, write everything (should not happen normally)
            size_t written = fwrite(buffer, 1, bufferFilled, f);
            fileSize += written;
            bufferFilled = 0;
            break;
        }

        size_t safeToWrite = (bufferFilled > boundaryLen) ? (bufferFilled - boundaryLen + 1) : 0;
        if (safeToWrite > 0) {
            size_t written = fwrite(buffer, 1, safeToWrite, f);
            if (written != safeToWrite) {
                fclose(f);
                unlink(fullPath);
                delete[] buffer;
                return sendJsonError(req, "Write failed");
            }
            fileSize += written;

            // Shift remaining data to front of buffer
            memmove(buffer, buffer + safeToWrite, bufferFilled - safeToWrite);
            bufferFilled -= safeToWrite;
        }
    }

    fclose(f);
    delete[] buffer;

    if (!boundaryFound) {
        ESP_LOGW(TAG, "Upload finished without finding boundary (file may be truncated)");
    }

    ESP_LOGI(TAG, "Upload complete: %s (%zu bytes)", fullPath, fileSize);

    cJSON* json = cJSON_CreateObject();
    cJSON_AddBoolToObject(json, "success", true);
    cJSON_AddStringToObject(json, "filename", filename);
    cJSON_AddNumberToObject(json, "size", (double)fileSize);
    return sendJsonObject(req, json);
}

// GET /api/files/download?file=/path/to/file - Download file
esp_err_t WebServer::filesDownloadHandler(httpd_req_t* req) {
    char file[256] = "";
    if (!getQueryParam(req, "file", file, sizeof(file)) || strlen(file) == 0) {
        httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing file parameter");
        return ESP_FAIL;
    }

    char fullPath[384];
    snprintf(fullPath, sizeof(fullPath), "%.7s%.255s", SdCard::MOUNT_POINT, file);

    const char* basename = strrchr(file, '/');
    basename = basename ? basename + 1 : file;
    char disposition[256];
    snprintf(disposition, sizeof(disposition), "attachment; filename=\"%.200s\"", basename);
    httpd_resp_set_hdr(req, "Content-Disposition", disposition);

    return sendFile(req, fullPath);
}

// DELETE /api/files/delete?file=/path/to/file - Delete file or empty directory
esp_err_t WebServer::filesDeleteHandler(httpd_req_t* req) {
    char file[256] = "";
    if (!getQueryParam(req, "file", file, sizeof(file)) || strlen(file) == 0) {
        return sendJsonError(req, "Missing file parameter");
    }

    if (!SdCard::instance().isMounted()) {
        return sendJsonError(req, "SD card not mounted");
    }

    char fullPath[384];
    snprintf(fullPath, sizeof(fullPath), "%.7s%.255s", SdCard::MOUNT_POINT, file);

    ESP_LOGI(TAG, "Deleting: %s", fullPath);

    struct stat st;
    if (stat(fullPath, &st) != 0) {
        return sendJsonError(req, "File not found");
    }

    int result;
    if (S_ISDIR(st.st_mode)) {
        result = rmdir(fullPath);
    } else {
        result = unlink(fullPath);
    }

    if (result == 0) {
        return sendJsonSuccess(req);
    }
    return sendJsonError(req, "Delete failed (directory not empty?)");
}

// POST /api/files/mkdir?path=/path/to/new/dir - Create directory
esp_err_t WebServer::filesMkdirHandler(httpd_req_t* req) {
    char path[256] = "";
    if (!getQueryParam(req, "path", path, sizeof(path)) || strlen(path) == 0) {
        return sendJsonError(req, "Missing path parameter");
    }

    if (!SdCard::instance().isMounted()) {
        return sendJsonError(req, "SD card not mounted");
    }

    char fullPath[384];
    snprintf(fullPath, sizeof(fullPath), "%.7s%.255s", SdCard::MOUNT_POINT, path);

    ESP_LOGI(TAG, "Creating directory: %s", fullPath);

    if (mkdir(fullPath, 0755) == 0) {
        return sendJsonSuccess(req);
    }
    return sendJsonError(req, "Cannot create directory");
}

// GET /api/files/read?file=/path/to/file - Read text file content for editor
esp_err_t WebServer::filesReadHandler(httpd_req_t* req) {
    char file[256] = "";
    if (!getQueryParam(req, "file", file, sizeof(file)) || strlen(file) == 0) {
        return sendJsonError(req, "Missing file parameter");
    }

    if (!SdCard::instance().isMounted()) {
        return sendJsonError(req, "SD card not mounted");
    }

    char targetPath[384];
    snprintf(targetPath, sizeof(targetPath), "%.7s%.255s", SdCard::MOUNT_POINT, file);

    char resolvedPath[384];
    const char* actualPath = targetPath;
    if (resolveCaseInsensitivePath(targetPath, resolvedPath, sizeof(resolvedPath))) {
        actualPath = resolvedPath;
    }

    struct stat st;
    if (stat(actualPath, &st) != 0) {
        return sendJsonError(req, "File not found");
    }

    if (st.st_size > 65536) {
        return sendJsonError(req, "File too large for editor (max 64KB)");
    }

    FILE* f = fopen(actualPath, "r");
    if (!f) {
        return sendJsonError(req, "Cannot open file");
    }

    char* content = new char[st.st_size + 1];
    if (!content) {
        fclose(f);
        return sendJsonError(req, "Out of memory");
    }

    size_t bytesRead = fread(content, 1, st.st_size, f);
    fclose(f);
    content[bytesRead] = '\0';

    httpd_resp_set_type(req, "text/plain; charset=utf-8");
    httpd_resp_send(req, content, bytesRead);

    delete[] content;
    return ESP_OK;
}

// POST /api/files/write?file=/path/to/file - Write text file content from editor
esp_err_t WebServer::filesWriteHandler(httpd_req_t* req) {
    char file[256] = "";
    if (!getQueryParam(req, "file", file, sizeof(file)) || strlen(file) == 0) {
        return sendJsonError(req, "Missing file parameter");
    }

    if (!SdCard::instance().isMounted()) {
        return sendJsonError(req, "SD card not mounted");
    }

    if (req->content_len > 65536) {
        return sendJsonError(req, "Content too large (max 64KB)");
    }

    char targetPath[384];
    snprintf(targetPath, sizeof(targetPath), "%.7s%.255s", SdCard::MOUNT_POINT, file);

    char resolvedPath[384];
    const char* actualPath = targetPath;
    if (resolveCaseInsensitivePath(targetPath, resolvedPath, sizeof(resolvedPath))) {
        actualPath = resolvedPath;
    }

    char* content = new char[req->content_len + 1];
    if (!content) {
        return sendJsonError(req, "Out of memory");
    }

    size_t received = 0;
    while (received < req->content_len) {
        int ret = httpd_req_recv(req, content + received, req->content_len - received);
        if (ret <= 0) {
            delete[] content;
            return sendJsonError(req, "Receive failed");
        }
        received += ret;
    }
    content[received] = '\0';

    ESP_LOGI(TAG, "Writing file: %s (%zu bytes)", actualPath, received);

    FILE* f = fopen(actualPath, "w");
    if (!f) {
        delete[] content;
        return sendJsonError(req, "Cannot create file");
    }

    size_t written = fwrite(content, 1, received, f);
    fclose(f);
    delete[] content;

    if (written != received) {
        return sendJsonError(req, "Write incomplete");
    }

    return sendJsonSuccess(req);
}
