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

static const char* TAG = "WebServer";

WebServer& WebServer::instance() {
    static WebServer instance;
    return instance;
}

WebServer::~WebServer() {
    stop();
}

void WebServer::start() {
    if (server_ != nullptr) {
        ESP_LOGW(TAG, "Server already running");
        return;
    }

    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.uri_match_fn = httpd_uri_match_wildcard;
    config.stack_size = 8192;
    config.max_uri_handlers = 24;

    ESP_LOGI(TAG, "Starting HTTP server on port %d", config.server_port);

    if (httpd_start(&server_, &config) != ESP_OK) {
        ESP_LOGE(TAG, "Failed to start HTTP server");
        return;
    }

    // Static file routes
    httpd_uri_t root = { .uri = "/", .method = HTTP_GET, .handler = rootHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &root);

    httpd_uri_t webseite = { .uri = "/webseite/*", .method = HTTP_GET, .handler = staticFileHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &webseite);

    httpd_uri_t images = { .uri = "/images/*", .method = HTTP_GET, .handler = imagesHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &images);

    // Config routes
    httpd_uri_t getConfig = { .uri = "/api/config", .method = HTTP_GET, .handler = getConfigHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &getConfig);

    httpd_uri_t postConfig = { .uri = "/api/config", .method = HTTP_POST, .handler = postConfigHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &postConfig);

    httpd_uri_t postSave = { .uri = "/api/save", .method = HTTP_POST, .handler = postSaveHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &postSave);

    httpd_uri_t postReset = { .uri = "/api/reset", .method = HTTP_POST, .handler = postResetHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &postReset);

    // KNX routes
    httpd_uri_t getKnxAddresses = { .uri = "/api/knx/addresses", .method = HTTP_GET, .handler = getKnxAddressesHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &getKnxAddresses);

    httpd_uri_t getKnxProg = { .uri = "/api/knx/prog", .method = HTTP_GET, .handler = getKnxProgHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &getKnxProg);

    httpd_uri_t postKnxProg = { .uri = "/api/knx/prog", .method = HTTP_POST, .handler = postKnxProgHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &postKnxProg);

    httpd_uri_t postKnxReset = { .uri = "/api/knx/reset", .method = HTTP_POST, .handler = postKnxResetHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &postKnxReset);

    // Status routes
    httpd_uri_t getStatus = { .uri = "/api/status", .method = HTTP_GET, .handler = getStatusHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &getStatus);

    // File manager routes
    httpd_uri_t fileManager = { .uri = "/files", .method = HTTP_GET, .handler = fileManagerHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &fileManager);

    httpd_uri_t filesList = { .uri = "/api/files/list", .method = HTTP_GET, .handler = filesListHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &filesList);

    httpd_uri_t filesUpload = { .uri = "/api/files/upload", .method = HTTP_POST, .handler = filesUploadHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &filesUpload);

    httpd_uri_t filesDownload = { .uri = "/api/files/download", .method = HTTP_GET, .handler = filesDownloadHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &filesDownload);

    httpd_uri_t filesDelete = { .uri = "/api/files/delete", .method = HTTP_DELETE, .handler = filesDeleteHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &filesDelete);

    httpd_uri_t filesMkdir = { .uri = "/api/files/mkdir", .method = HTTP_POST, .handler = filesMkdirHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &filesMkdir);

    httpd_uri_t filesRead = { .uri = "/api/files/read", .method = HTTP_GET, .handler = filesReadHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &filesRead);

    httpd_uri_t filesWrite = { .uri = "/api/files/write", .method = HTTP_POST, .handler = filesWriteHandler, .user_ctx = nullptr };
    httpd_register_uri_handler(server_, &filesWrite);

    ESP_LOGI(TAG, "HTTP server started successfully");
}

void WebServer::stop() {
    if (server_ != nullptr) {
        httpd_stop(server_);
        server_ = nullptr;
        ESP_LOGI(TAG, "HTTP server stopped");
    }
}

// ============================================================================
// Shared Utility Functions
// ============================================================================

const char* WebServer::getContentType(const char* filepath) {
    const char* ext = strrchr(filepath, '.');
    if (!ext) return "application/octet-stream";

    if (strcasecmp(ext, ".html") == 0 || strcasecmp(ext, ".htm") == 0) return "text/html; charset=utf-8";
    if (strcasecmp(ext, ".css") == 0) return "text/css";
    if (strcasecmp(ext, ".js") == 0) return "application/javascript";
    if (strcasecmp(ext, ".json") == 0) return "application/json";
    if (strcasecmp(ext, ".png") == 0) return "image/png";
    if (strcasecmp(ext, ".jpg") == 0 || strcasecmp(ext, ".jpeg") == 0) return "image/jpeg";
    if (strcasecmp(ext, ".gif") == 0) return "image/gif";
    if (strcasecmp(ext, ".svg") == 0) return "image/svg+xml";
    if (strcasecmp(ext, ".ico") == 0) return "image/x-icon";

    return "application/octet-stream";
}

// Case-insensitive path resolution for FAT filesystem
bool resolveCaseInsensitivePath(const char* requestedPath, char* resolvedPath, size_t maxLen) {
    strncpy(resolvedPath, SdCard::MOUNT_POINT, maxLen);
    resolvedPath[maxLen - 1] = '\0';

    const char* relPath = requestedPath;
    if (strncmp(requestedPath, SdCard::MOUNT_POINT, strlen(SdCard::MOUNT_POINT)) == 0) {
        relPath = requestedPath + strlen(SdCard::MOUNT_POINT);
    }

    if (!relPath || !*relPath || (relPath[0] == '/' && !relPath[1])) {
        return true;
    }

    char component[256];
    const char* start = relPath;

    while (*start) {
        while (*start == '/') start++;
        if (!*start) break;

        const char* end = start;
        while (*end && *end != '/') end++;

        size_t compLen = end - start;
        if (compLen >= sizeof(component)) compLen = sizeof(component) - 1;
        memcpy(component, start, compLen);
        component[compLen] = '\0';

        DIR* dir = opendir(resolvedPath);
        if (!dir) return false;

        bool found = false;
        struct dirent* entry;
        while ((entry = readdir(dir)) != nullptr) {
            if (strcasecmp(entry->d_name, component) == 0) {
                size_t curLen = strlen(resolvedPath);
                snprintf(resolvedPath + curLen, maxLen - curLen, "/%s", entry->d_name);
                found = true;
                break;
            }
        }
        closedir(dir);

        if (!found) return false;
        start = end;
    }

    return true;
}

esp_err_t WebServer::sendFile(httpd_req_t* req, const char* filepath) {
    char resolvedPath[384];
    const char* actualPath = filepath;

    struct stat st;
    if (stat(filepath, &st) != 0) {
        if (resolveCaseInsensitivePath(filepath, resolvedPath, sizeof(resolvedPath))) {
            if (stat(resolvedPath, &st) == 0) {
                actualPath = resolvedPath;
            }
        }
    }

    if (stat(actualPath, &st) != 0) {
        ESP_LOGE(TAG, "File not found: %s", filepath);
        httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File not found");
        return ESP_FAIL;
    }

    FILE* f = fopen(actualPath, "r");
    if (!f) {
        ESP_LOGE(TAG, "Failed to open file: %s", actualPath);
        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to open file");
        return ESP_FAIL;
    }

    httpd_resp_set_type(req, getContentType(actualPath));

    char* chunk = new char[1024];
    if (!chunk) {
        fclose(f);
        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Out of memory");
        return ESP_FAIL;
    }

    size_t read;
    while ((read = fread(chunk, 1, 1024, f)) > 0) {
        if (httpd_resp_send_chunk(req, chunk, read) != ESP_OK) {
            delete[] chunk;
            fclose(f);
            return ESP_FAIL;
        }
    }

    delete[] chunk;
    fclose(f);
    httpd_resp_send_chunk(req, nullptr, 0);
    return ESP_OK;
}

// URL decode helper (in-place)
void urlDecode(char* str) {
    char* dst = str;
    char* src = str;
    while (*src) {
        if (*src == '%' && src[1] && src[2]) {
            int hi = src[1];
            int lo = src[2];
            hi = (hi >= '0' && hi <= '9') ? hi - '0' :
                 (hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 :
                 (hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 : -1;
            lo = (lo >= '0' && lo <= '9') ? lo - '0' :
                 (lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 :
                 (lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 : -1;
            if (hi >= 0 && lo >= 0) {
                *dst++ = (char)((hi << 4) | lo);
                src += 3;
                continue;
            }
        } else if (*src == '+') {
            *dst++ = ' ';
            src++;
            continue;
        }
        *dst++ = *src++;
    }
    *dst = '\0';
}

bool WebServer::getQueryParam(httpd_req_t* req, const char* key, char* value, size_t maxLen) {
    size_t queryLen = httpd_req_get_url_query_len(req);
    if (queryLen == 0) return false;

    char* query = new char[queryLen + 1];
    if (!query) return false;

    if (httpd_req_get_url_query_str(req, query, queryLen + 1) != ESP_OK) {
        delete[] query;
        return false;
    }

    esp_err_t ret = httpd_query_key_value(query, key, value, maxLen);
    delete[] query;

    if (ret == ESP_OK) {
        urlDecode(value);
        return true;
    }
    return false;
}

// JSON response helpers
esp_err_t WebServer::sendJsonObject(httpd_req_t* req, cJSON* json) {
    char* str = cJSON_PrintUnformatted(json);
    if (!str) {
        cJSON_Delete(json);
        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "JSON print failed");
        return ESP_FAIL;
    }
    httpd_resp_set_type(req, "application/json");
    httpd_resp_send(req, str, strlen(str));
    free(str);
    cJSON_Delete(json);
    return ESP_OK;
}

esp_err_t WebServer::sendJsonError(httpd_req_t* req, const char* error) {
    cJSON* json = cJSON_CreateObject();
    cJSON_AddBoolToObject(json, "success", false);
    cJSON_AddStringToObject(json, "error", error);
    return sendJsonObject(req, json);
}

esp_err_t WebServer::sendJsonSuccess(httpd_req_t* req) {
    cJSON* json = cJSON_CreateObject();
    cJSON_AddBoolToObject(json, "success", true);
    return sendJsonObject(req, json);
}
