#include "my_wifi.h"
#include <Wifi.h>
#include <esp_wifi.h>
#include <esp_task_wdt.h>
#include <ESPmDNS.h>
#include <DNSServer.h>
#include "esp_log.h"
#include <FS.h>
#include <LittleFS.h>
#include <SD.h>
#include "common/fileSystem.h"
#include "global.h"
#include "my_buzzer.h"
#include <UriDecode.h>
#include <ArduinoJson.h>
#include "jsonconstrain.h"
#include "AppUpgrade.h"

static const char *tag = "WIFI";

volatile bool WifiClientConnected = false;
AsyncWebServer webServer(80);
AsyncWebSocket wsUpgradeProgress("/upgrade-progress");
//AsyncEventSource eventUpgradeProgress("/upgrade-progress");
//DNSServer *dnsServer;
//#define DNS_PORT 53

String client_ssid;
String client_pass;
String ap_ssid;
String ap_pass;

String mDnsName;
String HostName;

IPAddress local_IP(192,168,10,1);
IPAddress gateway(192,168,10,1);
IPAddress subnet(255,255,255,0);

// for file manager page
String filesDropdownOptions((char*)0);
String dirDropdownOptions((char*)0);
String savePath((char*)0); // needed for storing file when editing a file
String savePathInput((char*)0);
const char* http_username = "admin";
const char* http_password = "admin";
const char* param_delete_path = "delete-path";
const char* param_edit_path = "edit-path";
const char* param_dir_pad = "dir-path";
const char* param_edit_textarea = "edit-textarea";
const char* param_save_path = "save-path";
String allowedExtensionsForEdit = "txt, h, htm, html, css, cpp, js, json, ini, cfg";

volatile bool scanInProgress = false;

static String networkList = "";
int networkCount = 0;

volatile int scanStatus = 0;  // 0=none, 1=scanning, 2=complete, -1=error
String scanResults = "";      // Store scan results globally

TaskHandle_t Wifi_Task_Handle;

void Wifi_Task(void* parameter) {

  for(;;) {
    static String last_ssid = "";

    if (!WifiClientConnected || last_ssid != client_ssid) {
      WifiClientConnected = false; 
      ESP_LOGD(tag, "Wifi trying to connect to: %s", client_ssid.c_str());
      WiFi.disconnect();
      vTaskDelay(1000);
      WiFi.setAutoReconnect(false);
      WiFi.begin(client_ssid.c_str(), client_pass.c_str());
      vTaskDelay(1000);
      // wait up to 10 iterations for connection
      int attempts = 0;
      while (WiFi.status() != WL_CONNECTED && attempts < 10) {
        wl_status_t status = WiFi.status();
        switch (status) {
          case WL_NO_SSID_AVAIL:
            ESP_LOGW(tag, "No AP with SSID %s found", client_ssid.c_str());
            break;
          case WL_CONNECT_FAILED:
            ESP_LOGW(tag, "Connection failed (wrong password?)");
            break;
          case WL_DISCONNECTED:
            ESP_LOGD(tag, "Not connected yet... (attempt %d/10)", attempts + 1);
            break;
          default:
            ESP_LOGD(tag, "Status: %d", status);
            break;
        }
        vTaskDelay(2000);
        attempts++;
      }

      if(WiFi.status() == WL_CONNECTED){
        ESP_LOGD(tag, "Connected to %s", client_ssid.c_str());
        //mDnsName = WiFi.hostname();
        WifiClientConnected = true;
        WiFi.setAutoReconnect(true);
        last_ssid = client_ssid; 
        // Save the WiFi settings
        Wifi_Save_Credentials("/system/wifi.json");
      } else {
        ESP_LOGW(tag, "Failed to connect to %s", client_ssid.c_str());
      }
    }

    vTaskSuspend(Wifi_Task_Handle);
  }

  vTaskDelete(NULL);
}

void Wifi_Save_Credentials(String path) {
  // Load existing JSON
  JsonDocument doc;
  File readFile = LittleFS.open(path, "r");
  if (readFile) {
    DeserializationError error = deserializeJson(doc, readFile);
    readFile.close();
    if (error) {
      ESP_LOGE(tag, "Failed to parse existing JSON");
      return;
    }
  }

  // Update or create wifi-client section
  JsonObject wifiClient = doc["wifi-client"].to<JsonObject>();
  wifiClient["ssid"] = client_ssid;
  wifiClient["pass"] = client_pass;

  // Save updated JSON
  File writeFile = LittleFS.open(path, "w");
  if (!writeFile) {
    ESP_LOGE(tag, "Error opening %s for writing", path.c_str());
    return;
    }

    // Serialize JSON with pretty formatting
    if (serializeJsonPretty(doc, writeFile) == 0) {
    ESP_LOGE(tag, "Failed to write JSON to file");
    }
  writeFile.close();
}

void Wifi_Init() {
  // Initialize LittleFS
  if (!LittleFS.begin(true)) {
    ESP_LOGE(tag, "LittleFS mount failed");
    return;
  }

  // Set Wi-Fi task to run on Core 1
  esp_wifi_set_ps(WIFI_PS_NONE); // Disable power save mode for better responsiveness

  // Set WiFi to AP+STA mode
  WiFi.mode(WIFI_MODE_APSTA);

  Wifi_Scan_for_Networks();

  // Configure and start AP
  WiFi.softAPConfig(local_IP, gateway, subnet);
  if (!WiFi.softAP(ap_ssid, ap_pass)) {
    ESP_LOGE(tag, "AP start failed");
    return;
  }

  // Add CORS headers
  DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
  DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT");
  DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type");

  Setup_WebServer_Handlers(webServer);

  WiFi.onEvent(onWiFiEvent);
  WiFi.setHostname(mDnsName.c_str());

  webServer.begin();
  ESP_LOGD(tag, "AP started with IP: %s", WiFi.softAPIP().toString().c_str());

  // Start the WiFi task
  xTaskCreatePinnedToCore(Wifi_Task, "Wifi_Task", 1024*4, NULL, 1, &Wifi_Task_Handle, 0);
}

void Wifi_Load_Settings(String path){
  // Load WiFi settings
  File file = LittleFS.open(path, "r");
  if (!file) {
    ESP_LOGE(tag, "Error opening %s", path.c_str());
    return;
  }

  JsonDocument doc;
  DeserializationError error = deserializeJson(doc, file);
  file.close();

  if (error) {
    ESP_LOGE(tag, "Failed to deserialize %s", path.c_str());
    return;
  }

  JsonObject wifiJson = doc.as<JsonObject>();
  if (wifiJson.isNull()) {
    ESP_LOGE(tag, "%s is empty", path.c_str());
    return;
  }

  // Load AP settings
  JsonObject apJson = wifiJson["wifi-ap"];
  if (!apJson.isNull()) {
    ap_ssid = jsonConstrainString(tag, apJson, "ssid", "ATA-AP");
    ap_pass = jsonConstrainString(tag, apJson, "pass", "12345678");
    local_IP.fromString(jsonConstrainString(tag, apJson, "ip", "192.168.10.1"));
    gateway.fromString(jsonConstrainString(tag, apJson, "gateway", "192.168.10.1"));
    subnet.fromString(jsonConstrainString(tag, apJson, "subnet", "255.255.255.0"));
  }

  // Load Client settings
  JsonObject clientJson = wifiJson["wifi-client"];
  if (!apJson.isNull()) {
    client_ssid = jsonConstrainString(tag, clientJson, "ssid", "none");
    client_pass = jsonConstrainString(tag, clientJson, "pass", "12345678");
  }
}

void Wifi_Scan_for_Networks(){
  // Start a scan for available networks
  WiFi.scanNetworks(false, false);
  while (WiFi.scanComplete() == WIFI_SCAN_RUNNING) {
    vTaskDelay(100);  // Wait for scan to complete
  }
  
  networkCount = WiFi.scanComplete();
  if (networkCount >= 0) {
    JsonDocument doc;
    JsonArray networks = doc["networks"].to<JsonArray>();
    
    for (int i = 0; i < networkCount; i++) {
      auto network = networks.add<JsonObject>();
      network["ssid"] = WiFi.SSID(i);
      network["rssi"] = WiFi.RSSI(i);
      network["encryption"] = WiFi.encryptionType(i) != WIFI_AUTH_OPEN;
    }
    
    String jsonString;
    serializeJson(doc, jsonString);
    networkList = jsonString;
    WiFi.scanDelete();
  } else {
    ESP_LOGE(tag, "WiFi scan failed");
  }
}

void Setup_WebServer_Handlers(AsyncWebServer& server){

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
      request->send(LittleFS, "/www/index.html", "text/html");
  });
  server.on("/home", HTTP_GET, [](AsyncWebServerRequest *request){
    sendHtmlFile("/www/home.html", request, HomeHtmlProcessor);
  });
  server.on("/setup", HTTP_GET, [](AsyncWebServerRequest *request){
    if(!request->authenticate(http_username, http_password)){
      return request->requestAuthentication();
    }
    //sendHtmlFile("/www/setup.html", request, htmlProcessor);
  });
  server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
    sendHtmlFile("/www/about.html", request, fileManagerHtmlProcessor);
    //request->send(LittleFS, "/www/about.html", "text/html");
  }); 


  // Wifi related handlers
  server.on("/wifi/connect", HTTP_POST, [](AsyncWebServerRequest *request){
    if (request->hasParam("ssid", false, false) && request->hasParam("pass", false, false)) {
      client_ssid = request->getParam("ssid", false, false)->value().c_str();
      if (Wifi_Task_Handle != NULL && eTaskGetState(Wifi_Task_Handle) == eSuspended) {
          ESP_LOGD(tag, "Resuming WiFi task");
          WifiClientConnected = false;
          vTaskResume(Wifi_Task_Handle);
      } else {
          ESP_LOGE(tag, "Failed to resume WiFi task: invalid handle or task not suspended");
      }
      vTaskResume(Wifi_Task_Handle);
      request->send(200, "application/json", "{\"status\":\"connecting\"}");
    } else {
      ESP_LOGE(tag, "Missing ssid or pass parameter");
      request->send(400, "application/json", "{\"error\":\"Missing ssid or pass parameter\"}");
    }
  }); 
  server.on("/wifi/status", HTTP_GET, [](AsyncWebServerRequest *request){
      String jsonStr = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + 
                       "\",\"ip\":\"" + WiFi.localIP().toString() + "\"}";
      request->send(200, "application/json", jsonStr);
  });
  server.on("/wifi/scans", HTTP_GET, [](AsyncWebServerRequest *request){
      if(networkCount <= 0) {
          request->send(400, "application/json", "{\"error\":\"No scan results\"}");
          return;
      }
      
      request->send(200, "application/json", networkList);
  });
  server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){
    if(WiFi.getMode() == WIFI_MODE_APSTA){
      // TODO Disable navigation bar
    }
    //sendHtmlFile("/www/wifi.html", request, htmlProcessor);
    request->send(LittleFS, "/www/wifi.html", "text/html");
  }); 


  // File Manager related handlers
  server.on("/files/upload", HTTP_POST, [](AsyncWebServerRequest *request) { request->send(200); }, handleFilesUpload_OnBody);

  server.on("/files/download", HTTP_GET, [](AsyncWebServerRequest *request){
    if (!request->hasParam("file")) {
        ESP_LOGE(tag, "Missing file parameter");
        request->send(400, "text/plain", "Missing file parameter");
        return;
    }

    try {
        String filename = uriDecode(request->getParam("file")->value());
        ESP_LOGD(tag, "Download request for: %s", filename.c_str());
        request->send(LittleFS, filename, "application/octet-stream");
    }
    catch (const std::exception& e) {
        ESP_LOGE(tag, "Download failed: %s", e.what());
        request->send(404, "text/plain", "File not found!");
    }
  });
  server.on("/files/delete", HTTP_GET, [](AsyncWebServerRequest *request) {
      static const char* tag = "DeleteHandler";

      // Authentication check
      if (!request->authenticate(http_username, http_password)) {
          ESP_LOGW(tag, "Authentication failed for delete request");
          return request->requestAuthentication();
      }

      // Parameter validation
      if (!request->hasParam(param_delete_path)) {
          ESP_LOGE(tag, "Missing delete path parameter");
          request->send(400, "text/plain", "Missing file path");
          return;
      }

      // Get and validate filename
      String filename = uriDecode(request->getParam(param_delete_path)->value());
      if (filename == "choose") {
          request->redirect("/files");
          return;
      }

      // Ensure path starts with /
      if (!filename.startsWith("/")) {
          filename = "/" + filename;
      }

      try {
          if (LittleFS.remove(filename.c_str())) {
              ESP_LOGI(tag, "Successfully deleted file: %s", filename.c_str());
          } else {
              ESP_LOGE(tag, "Failed to delete file: %s", filename.c_str());
              request->send(500, "text/plain", "Delete failed");
              return;
          }
      } catch (const std::exception& e) {
          ESP_LOGE(tag, "Exception during delete: %s", e.what());
          request->send(500, "text/plain", "Delete failed");
          return;
      }

      request->redirect("/files");
  });
  server.on("/files/edit", HTTP_GET, [](AsyncWebServerRequest *request) {
      static const char* tag = "EditHandler";

      // Authentication check
      if (!request->authenticate(http_username, http_password)) {
          ESP_LOGW(tag, "Authentication failed");
          return request->requestAuthentication();
      }

      // Parameter validation
      if (!request->hasParam(param_edit_path)) {
          ESP_LOGE(tag, "Missing edit path parameter");
          request->send(400, "text/plain", "Missing file path");
          return;
      }

      // Get and decode filename
      String fileName = uriDecode(request->getParam(param_edit_path)->value());
      ESP_LOGD(tag, "Edit request for file: %s", fileName.c_str());

      // Set save path
      savePath = (fileName == "new") ? "/new.txt" : fileName;

      // Validate path
      if (!savePath.startsWith("/")) {
          savePath = "/" + savePath;
      }

      ESP_LOGD(tag, "Save path set to: %s", savePath.c_str());
      
      try {
          sendHtmlFile("/www/edit.html", request, fileManagerHtmlProcessor);
      } catch (const std::exception& e) {
          ESP_LOGE(tag, "Failed to send edit page: %s", e.what());
          request->send(500, "text/plain", "Internal server error");
      }
  });
  server.on("/files/save", HTTP_GET, [](AsyncWebServerRequest *request){
    if(!request->authenticate(http_username, http_password)){
      return request->requestAuthentication();
    }
    String inputMessage((char*)0);
    if (request->hasParam(param_edit_textarea)) {
      inputMessage = request->getParam(param_edit_textarea)->value();
    }
    if (request->hasParam(param_save_path)) {
      savePath = uriDecode(request->getParam(param_save_path)->value());
    }
    writeFile(LittleFS, savePath.c_str(), inputMessage.c_str());

    request->redirect("/files");
  });
  server.on("/files", HTTP_GET, [](AsyncWebServerRequest *request){
    if(!request->authenticate(http_username, http_password)){
      return request->requestAuthentication();
    }
    sendHtmlFile("/www/files.html", request, fileManagerHtmlProcessor);
  });


  // Lights related handlers
  server.on("/lights/settings", HTTP_GET, [](AsyncWebServerRequest *request) {
      request->send(200);
  });
  server.on("/lights/settings", HTTP_POST, [](AsyncWebServerRequest *request) {
      request->send(200);
  });
  server.on("/lights/animation", HTTP_POST, [](AsyncWebServerRequest *request) {
      request->send(200);
  });
  server.on("/lights/setpixel", HTTP_POST, [](AsyncWebServerRequest *request) {
      request->send(200);
  });


  // System and LED related handlers
  server.on("/system/summary", HTTP_GET, [](AsyncWebServerRequest *request){
      String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
      request->send(200, "application/json", response);
  });
  server.on("/leds/settings", HTTP_GET, [](AsyncWebServerRequest *request){
    /*
      //CreateSysSummmaryPacket(doc);
      String summary;
      serializeJson(jsDoc, summary); 
      request->send(200, "application/json", summary);
    */
      String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
      request->send(200, "application/json", response);
  });
  server.on("/leds/settings", HTTP_POST, [](AsyncWebServerRequest *request){
      String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
      request->send(200, "application/json", response);
  });



  // LightStik related handlers
  server.on("/lightstik/settings", HTTP_GET, [](AsyncWebServerRequest *request){
      String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
      request->send(200, "application/json", response);
  });
  server.on("/lightstik/settings", HTTP_POST, [](AsyncWebServerRequest *request){
      String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
      request->send(200, "application/json", response);
  });
  server.on("/lightstik/register", HTTP_POST, [](AsyncWebServerRequest *request){
      String response = "{\"status\":\"" + String(WifiClientConnected ? "Connected" : "Disconnected") + "\"}";
      request->send(200, "application/json", response);
  });


  // Firmware Update Handlers
  server.on("/upgrade/check", HTTP_GET, [](AsyncWebServerRequest *request) {
      JsonDocument doc;
      doc["currentVersion"] = FIRMWARE_VERSION;
      doc["latestVersion"] = "1.1.0";  
      doc["updateAvailable"] = true;    
      
      String response;
      serializeJson(doc, response);
      request->send(200, "application/json", response);
  });
  // Start update process
  server.on("/upgrade/start", HTTP_POST, [](AsyncWebServerRequest *request) {
      //if (!request->authenticate(http_username, http_password)) {
      //    return request->requestAuthentication();
      //}
      startFirmwareUpdateTask(&wsUpgradeProgress);
      request->send(200);
  });
  //server.on("/upgrade/progress", HTTP_GET, [](AsyncWebServerRequest *request) {
    /*
    if (!request->authenticate(http_username, http_password)) {
        return request->requestAuthentication();
    }
    AsyncWebServerResponse *response = request->beginResponse(200, "text/event-stream");
    response->addHeader("Cache-Control", "no-cache");
    response->addHeader("Connection", "keep-alive");
    request->send(response);
    */
  //});
  server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest *request) {
    if(!request->authenticate(http_username, http_password)){
      return request->requestAuthentication();
    }
    request->send(LittleFS, "/www/upgrade.html", "text/html");
  });

  wsUpgradeProgress.onEvent(onWsUpdateProgressEvent);
  server.addHandler(&wsUpgradeProgress);

  // Basic Connection status check
  server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request) {
      request->send(200, "application/json", "{\"status\":\"connected\"}");
  });
  // Server requested files that aren't template processed
  server.on("/*", HTTP_GET, [](AsyncWebServerRequest *request) {  // handle file uploads
     // Validate request
    if (!request) {
        ESP_LOGE(tag, "Invalid request");
        return;
    }

    // Get and validate file path
    String filePath = request->url();
    if (filePath.isEmpty()) {
        ESP_LOGE(tag, "Empty file path");
        request->send(400, "text/plain", "Invalid file path");
        return;
    }

    // Ensure path starts with '/'
    if (!filePath.startsWith("/")) {
        filePath = "/" + filePath;
    }

    try {
        // Get content type once
        const char* contentType = getFileType(getFileExtension(filePath.c_str()));
        if (!contentType) {
            ESP_LOGW(tag, "Unknown file type: %s", filePath.c_str());
            contentType = "application/octet-stream";
        }

        ESP_LOGD(tag, "Sending file: %s (%s)", filePath.c_str(), contentType);
        request->send(LittleFS, filePath, contentType);
    } 
    catch (const std::runtime_error& e) {
        ESP_LOGE(tag, "FileSystem error: %s for path: %s", e.what(), filePath.c_str());
        request->send(404, "text/plain", "File not found");
    }
    catch (const std::exception& e) {
        ESP_LOGE(tag, "Error: %s for path: %s", e.what(), filePath.c_str());
        request->send(500, "text/plain", "Internal server error");
    }
  });
  // 404 handler
  server.onNotFound([](AsyncWebServerRequest *request) {
      ESP_LOGE(tag, "404: %s", request->url().c_str());
      request->send(404, "text/plain", "Not found");
  });

}

void onWsUpdateProgressEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      ESP_LOGD(tag,"WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      break;
    case WS_EVT_DISCONNECT:
      ESP_LOGD(tag, "WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
      ESP_LOGD(tag, "WebSocket client #%u data\n", client->id());
      //handleWebSocketMessage(arg, data, len);
      break;
    case WS_EVT_PONG:
      ESP_LOGD(tag, "WebSocket client #%u pong\n", client->id());
      break;   
    case WS_EVT_ERROR:
      ESP_LOGD(tag, "WebSocket client #%u error\n", client->id());
      break;
  }
}



void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
    static const size_t MAX_UPLOAD_SIZE = 1024 * 1024; // 1MB limit
    
    if (!index) {
        // Initial upload chunk
        if (!request->hasParam("dir-path", true, false)) {
            ESP_LOGE(tag, "Missing dir-path parameter");
            request->send(400, "text/plain", "Missing dir-path");
            return;
        }

        AsyncWebParameter* p = request->getParam("dir-path", true, false);
        String path = p->value() + "/" + filename;
        ESP_LOGD(tag, "Starting upload: %s", path.c_str());

        // Validate path
        if (!path.startsWith("/")) {
            path = "/" + path;
        }

        request->_tempFile = LittleFS.open(path, "w");
        if (!request->_tempFile) {
            ESP_LOGE(tag, "Failed to create file: %s", path.c_str());
            request->send(500, "text/plain", "Failed to create file");
            return;
        }
    }

    // Write chunk
    if (len && request->_tempFile) {
        if (index + len > MAX_UPLOAD_SIZE) {
            request->_tempFile.close();
            LittleFS.remove(request->_tempFile.name());
            ESP_LOGE(tag, "Upload too large");
            request->send(413, "text/plain", "File too large");
            return;
        }

        if (!request->_tempFile.write(data, len)) {
            ESP_LOGE(tag, "Write failed");
            request->_tempFile.close();
            request->send(500, "text/plain", "Write failed");
            return;
        }
    }

    if (final) {
        request->_tempFile.close();
        ESP_LOGD(tag, "Upload complete: %s, %u bytes", filename.c_str(), index + len);
        request->redirect("/files");
    }
}


// Send html file with template processing {{VAR}}
void sendHtmlFile(const char* filePath, AsyncWebServerRequest *request, String (*callback)(const String&)) {
    try {
        const char* htmlFile = readFile(LittleFS, filePath);
        if (!htmlFile) {
            ESP_LOGE(tag, "Failed to read file: %s", filePath);
            request->send(404, "text/plain", "File not found");
            return;
        }

        String processedData = varReplace(htmlFile, callback);
        delete[] htmlFile;  // Clean up allocated memory
        
        ESP_LOGD(tag, "Sent file: %s", filePath);
        request->send(200, "text/html", processedData);
    } 
    catch (const std::exception& e) {
        ESP_LOGE(tag, "Error processing file %s: %s", filePath, e.what());
        request->send(500, "text/plain", "Server error");
    }
}

const char* getFileExtension(const char* filename) {
    // Input validation
    if (!filename) {
        ESP_LOGW(tag, "Null filename provided");
        return "";
    }

    // Find last dot
    const char* lastDot = strrchr(filename, '.');
    if (!lastDot || lastDot == filename || *(lastDot + 1) == '\0') {
        ESP_LOGD(tag, "No valid extension found in: %s", filename);
        return "";
    }

    ESP_LOGD(tag, "Found extension: %s", lastDot + 1);
    return lastDot + 1;
}

const char* getFileType(const char* ext) {
    if (!ext) return "application/octet-stream";

    ESP_LOGD(tag, "Getting file type for extension: %s", ext);

    if (strcmp(ext, "png") == 0) return "image/png";
    if (strcmp(ext, "jpg") == 0 || strcmp(ext, "jpeg") == 0) return "image/jpeg";
    if (strcmp(ext, "gif") == 0) return "image/gif";
    if (strcmp(ext, "ico") == 0) return "image/x-icon";
    if (strcmp(ext, "txt") == 0) return "text/plain";
    if (strcmp(ext, "css") == 0) return "text/css";
    if (strcmp(ext, "htm") == 0 || strcmp(ext, "html") == 0) return "text/html";
    if (strcmp(ext, "js") == 0) return "text/javascript";
    if (strcmp(ext, "json") == 0) return "application/json";
    
    ESP_LOGW(tag, "Unknown file extension: %s", ext);
    return "application/octet-stream";
}

// Finds segments between {{VAR}} and calls a callback function to replace VAR with new content
String varReplace(const String& input, String (*callback)(const String&)) {
    static const char* tag = "varReplace";
    
    // Validate inputs
    if (input.isEmpty() || !callback) {
        ESP_LOGW(tag, "Empty input or null callback");
        return input;
    }

    // Pre-allocate result string with estimated size
    String result;
    result.reserve(input.length() * 1.2); // Add 20% for potential replacements

    const int maxSegmentLength = 32;
    int startPos = 0;
    
    // Process all segments
    while (true) {
        // Find next variable
        int start = input.indexOf("{{", startPos);
        if (start == -1) {
            break;
        }

        // Add text before variable
        result += input.substring(startPos, start);

        // Find end of variable
        int end = input.indexOf("}}", start + 2);
        if (end == -1) {
            ESP_LOGW(tag, "Unmatched {{ at position %d", start);
            result += input.substring(start);
            break;
        }

        // Extract and validate segment
        String segment = input.substring(start + 2, end);
        if (segment.length() <= maxSegmentLength) {
            try {
                String replacement = callback(segment);
                result += replacement;
            } catch (const std::exception& e) {
                ESP_LOGE(tag, "Callback error: %s", e.what());
                result += input.substring(start, end + 2);
            }
        } else {
            ESP_LOGW(tag, "Segment too long: %d chars", segment.length());
            result += input.substring(start, end + 2);
        }

        startPos = end + 2;
    }

    // Add remaining text
    if (startPos < input.length()) {
        result += input.substring(startPos);
    }

    return result;
}

const char* convertFileSize(const size_t bytes) {
    static char fileSizeBuffer[16]; // Pre-allocated buffer for the file size

    if (bytes < 1024) {
        snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%d B", bytes);
    } else if (bytes < 1024 * 1024) {
        snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f kB", bytes / 1024.0);
    } else if (bytes < 1024 * 1024 * 1024) {
        snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f MB", bytes / (1024.0 * 1024.0));
    } else {
        snprintf(fileSizeBuffer, sizeof(fileSizeBuffer), "%.2f GB", bytes / (1024.0 * 1024.0 * 1024.0));
    }

    return fileSizeBuffer;
}

void onWiFiEvent(WiFiEvent_t event) {
    //Serial.printf("[WiFi-event] event: %d\n", event);
    switch (event) {
        case ARDUINO_EVENT_WIFI_READY: 
            ESP_LOGD(tag, "WiFi interface ready");
            break;
        case ARDUINO_EVENT_WIFI_SCAN_DONE:
            ESP_LOGD(tag,"Completed scan for access points");
            break;
        case ARDUINO_EVENT_WIFI_STA_START:
            ESP_LOGD(tag,"WiFi client started");
            break;
        case ARDUINO_EVENT_WIFI_STA_STOP:
            ESP_LOGD(tag,"WiFi clients stopped");
            break;
        case ARDUINO_EVENT_WIFI_STA_CONNECTED:
            ESP_LOGD(tag,"Connected to AP");
            break;
        case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
            WifiClientConnected = false;
            ESP_LOGD(tag, "WiFi Disconnected");
            Buzzer_Play_Tune(TUNE_DISCONNECTED); 
            break;
        case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE:
            ESP_LOGD(tag,"Authentication mode of access point has changed");
            break;
        case ARDUINO_EVENT_WIFI_STA_GOT_IP:
            WifiClientConnected = true;
            ESP_LOGD(tag,"My IP: %s", WiFi.localIP().toString());
            //Wifi_Start_MDNS();
            Buzzer_Play_Tune(TUNE_CONNECTED); 
            break;
        case ARDUINO_EVENT_WIFI_STA_LOST_IP:
            WifiClientConnected = false;
            ESP_LOGD(tag,"Lost IP address and IP address is reset to 0");
            break;
        case ARDUINO_EVENT_WPS_ER_SUCCESS:
            ESP_LOGD(tag,"WiFi Protected Setup (WPS): succeeded in enrollee mode");
            break;
        case ARDUINO_EVENT_WPS_ER_FAILED:
            ESP_LOGD(tag,"WiFi Protected Setup (WPS): failed in enrollee mode");
            break;
        case ARDUINO_EVENT_WPS_ER_TIMEOUT:
            ESP_LOGD(tag,"WiFi Protected Setup (WPS): timeout in enrollee mode");
            break;
        case ARDUINO_EVENT_WPS_ER_PIN:
            ESP_LOGD(tag,"WiFi Protected Setup (WPS): pin code in enrollee mode");
            break;
        case ARDUINO_EVENT_WIFI_AP_START:
            ESP_LOGD(tag, "WiFi access point started");
            break;
        case ARDUINO_EVENT_WIFI_AP_STOP:
            ESP_LOGD(tag, "WiFi access point  stopped");
            break;
        case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
            ESP_LOGD(tag, "Client connected");
            break;
        case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
            ESP_LOGD(tag, "SoftAP Client Disconnected");
            Buzzer_Play_Tune(TUNE_DISCONNECTED); 
            break;
        case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED:
            ESP_LOGD(tag,"SoftAP Client Connected");
            Buzzer_Play_Tune(TUNE_CONNECTED); 
            break;
        case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED:
            ESP_LOGD(tag,"Received probe request");
            break;
        case ARDUINO_EVENT_WIFI_AP_GOT_IP6:
            ESP_LOGD(tag,"AP IPv6 is preferred");
            break;
        case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
            ESP_LOGD(tag,"STA IPv6 is preferred");
            break;
        case ARDUINO_EVENT_ETH_GOT_IP6:
            ESP_LOGD(tag,"Ethernet IPv6 is preferred");
            break;
        case ARDUINO_EVENT_ETH_START:
            ESP_LOGD(tag,"Ethernet started");
            break;
        case ARDUINO_EVENT_ETH_STOP:
            ESP_LOGD(tag,"Ethernet stopped");
            break;
        case ARDUINO_EVENT_ETH_CONNECTED:
            ESP_LOGD(tag,"Ethernet connected");
            break;
        case ARDUINO_EVENT_ETH_DISCONNECTED:
            ESP_LOGD(tag,"Ethernet disconnected");
            break;
        case ARDUINO_EVENT_ETH_GOT_IP:
            ESP_LOGD(tag,"Obtained IP address");
            break;
        default: break;
    }
}

void Wifi_Start_MDNS(void) {
  ESP_LOGV(tag, "Initializing MDNS: %s", mDnsName.c_str());
  if (!MDNS.begin(mDnsName.c_str())) {
    ESP_LOGE(tag, "Error setting up MDNS responder!");
  }else{
    ESP_LOGV(tag, "You can access device via http://%s.local", mDnsName);
  }
}

bool writeFile(fs::FS &fs, const char* path, const char* message) {
    // Validate inputs
    if (!path || !message) {
        ESP_LOGE(tag, "Invalid parameters: path=%p message=%p", path, message);
        return false;
    }

    // Open file with error checking
    File file = fs.open(path, "w");
    if (!file) {
        ESP_LOGE(tag, "Failed to open file: %s", path);
        return false;
    }

    // Write with error handling
    try {
        size_t bytesWritten = file.print(message);
        if (bytesWritten == 0) {
            ESP_LOGE(tag, "Failed to write to file: %s", path);
            file.close();
            return false;
        }
        
        // Ensure all data is written
        file.flush();
        file.close();
        ESP_LOGD(tag, "Successfully wrote %u bytes to %s", bytesWritten, path);
        return true;
    }
    catch (const std::exception& e) {
        ESP_LOGE(tag, "Exception while writing file %s: %s", path, e.what());
        file.close();
        return false;
    }
}

char* readFile(fs::FS &fs, const char* path) {
    static const char* tag = "readFile";
    static const size_t MAX_FILE_SIZE = 1024 * 1024;  // 1MB limit

    // Validate input
    if (!path) {
        ESP_LOGE(tag, "Invalid path parameter");
        return nullptr;
    }

    // Open file
    File file = fs.open(path, "r");
    if (!file || file.isDirectory()) {
        ESP_LOGE(tag, "Failed to open file: %s", path);
        return nullptr;
    }

    // Check file size
    size_t fileSize = file.size();
    if (fileSize == 0 || fileSize > MAX_FILE_SIZE) {
        ESP_LOGE(tag, "Invalid file size: %u bytes", fileSize);
        file.close();
        return nullptr;
    }

    // Allocate memory
    char* fileContent = new (std::nothrow) char[fileSize + 1];
    if (!fileContent) {
        ESP_LOGE(tag, "Memory allocation failed for size: %u", fileSize + 1);
        file.close();
        return nullptr;
    }

    // Read file
    size_t bytesRead = file.readBytes(fileContent, fileSize);
    file.close();

    if (bytesRead != fileSize) {
        ESP_LOGE(tag, "Read failed: expected %u bytes, got %u", fileSize, bytesRead);
        delete[] fileContent;
        return nullptr;
    }

    // Null terminate
    fileContent[bytesRead] = '\0';
    ESP_LOGD(tag, "Successfully read %u bytes from %s", bytesRead, path);
    return fileContent;
}

String getSoftAPMacAddress() {
  uint8_t mac[6];
  WiFi.softAPmacAddress(mac);

  String macString = "";
  for (int i = 0; i < 6; i++) {
      macString += String(mac[i], HEX);
      if (i < 5) macString += ":";
  }
  return macString;
}

String listDirAsHtml(String directoryList[], int count) {
  String listedFiles;

  for (int i = 0; i < count; i++) {
    // directory html
    listedFiles += "<tr id=\"file-row\"><td>Dir: ";
      listedFiles += directoryList[i];
      listedFiles += "/</td><td>-</td></tr>\n";

    filesDropdownOptions += "<option value=\"";
      filesDropdownOptions += directoryList[i];
      filesDropdownOptions += "\">";
      filesDropdownOptions += directoryList[i];
      filesDropdownOptions += "/</option>\n";

    dirDropdownOptions += "<option value=\"";
      dirDropdownOptions += directoryList[i];
      dirDropdownOptions += "\">";
      dirDropdownOptions += directoryList[i];
      dirDropdownOptions += "/</option>\n";

    File dir = LittleFS.open(directoryList[i]);
    File file = dir.openNextFile();
    while (file) {
      String fileName = file.name();
      if (!file.isDirectory()) {
        //Serial.println("    File: " + String(file.name()));
        listedFiles += "<tr id=\"file-row\"><td>&emsp;<button onclick=\"window.location.href='/download?file=";
          listedFiles += file.path();
          listedFiles += "\" \"\">&#8681;</button>&emsp;";
          listedFiles += fileName;
          listedFiles += "</td><td>";
          listedFiles += convertFileSize(file.size());
          listedFiles += "</td></tr>\n";

        filesDropdownOptions += "<option value=\"";
          filesDropdownOptions += file.path();
          filesDropdownOptions += "\">&emsp;&emsp;";
          filesDropdownOptions += fileName;
          filesDropdownOptions += "</option>\n";
      }
      file = dir.openNextFile();
    }
    dir.close();
  }

  return listedFiles;
}



/********************  Specific Html Processors  ********************/
// file manager html processor
String fileManagerHtmlProcessor(const String& var){
  if(var == "ALLOWED_EXTENSIONS_EDIT"){ return allowedExtensionsForEdit; }

  if(var == "FS_FREE_BYTES"){ return convertFileSize(LittleFS.totalBytes() - LittleFS.usedBytes()); }

  if(var == "FS_USED_BYTES"){ return convertFileSize(LittleFS.usedBytes()); }

  if(var == "FS_TOTAL_BYTES"){ return convertFileSize(LittleFS.totalBytes()); }

  if(var == "RAM_FREE_BYTES"){ return convertFileSize(LittleFS.totalBytes()); }

  if(var == "RAM_USED_BYTES"){ return convertFileSize(LittleFS.totalBytes()); }

  if(var == "RAM_TOTAL_BYTES"){ return convertFileSize(LittleFS.totalBytes()); }
  
  if(var == "LISTED_FILES"){
    filesDropdownOptions = ""; // clear out 
    dirDropdownOptions = ""; // clear out
    String directories[MAX_DIRECTORIES];
    int dirCount = 0;
    getAllDirectories(directories, dirCount);
    return listDirAsHtml(directories, dirCount);
  }

  if(var == "EDIT-DEL_FILES"){ return filesDropdownOptions; }
  
  if(var == "DIR_LIST"){ return dirDropdownOptions; }
  
  if(var == "SAVE_PATH_INPUT"){ return  savePath; }

  if(var == "FIRM_VER"){ return FIRMWARE_VERSION; }

  return var;
}

String HomeHtmlProcessor(const String& var) {
  if (var == "APP_NAME") {
    //return sysProps.appName;
    return "N/A";  
  }
  if (var == "OLED") {
    return "N/A";
  }
  if (var == "STRIP1") {
    //return (strip1) ? "Yes" : "No";
    return "N/A";
  }
  if (var == "STRIP2") {
    //return (strip2) ? "Yes" : "No";
    return "N/A";
  }
  if (var == "FRONT_LIGHT") {
    //return (animProps.frontLight.enabled) ? "Yes" : "No";
    return "N/A";
  }
  if (var == "REAR_LIGHT") {
    //return (animProps.rearLight.enabled) ? "Yes" : "No";
    return "N/A";
  }
  if (var == "FIRMWARE") {
    return FIRMWARE_VERSION;
  }
  if (var == "BOOTH_T") {
    //return String(sysProps.t_sensor.temperature) + "F";
    return "N/A";
  }
  if (var == "SETPOINT") {
    //return String(sysProps.t_sensor.Setpoint1) + "F";
    return "N/A";
  }
  if (var == "FLASH_SIZE") { return convertFileSize(ESP.getSketchSize());}
  if (var == "FLASH_FREE") { return convertFileSize(ESP.getFreeSketchSpace());}
  if (var == "HEAP_SIZE") { return convertFileSize(ESP.getHeapSize()); }
  if (var == "HEAP_FREE") { return convertFileSize(ESP.getFreeHeap()); }
  if (var == "CPU_FREQ") { return String(ESP.getCpuFreqMHz()) + "Mhz"; }
  if (var == "IP") { return WiFi.localIP().toString(); }
  if (var == "MAC") { return chipInfo.macStr; }
  if (var == "SSID") { return WiFi.SSID(); }
  if (var == "RSSI") { return String(WiFi.RSSI()); }
  if (var == "WIFI_CH") { return String(WiFi.channel()); }
  if (var == "ENCRYP") { return String(WiFi.encryptionType(0)); }
  if (var == "AP_SSID") { return WiFi.softAPSSID(); }
  if (var == "AP_CLIENTS") { return String(WiFi.softAPgetStationNum()); }
  if (var == "BLE") { return (commMode == COMM_WIFI_AP_BLE) ? "Yes" : "No"; }
  if (var == "BLE_SSID") {
    //return (commMode == COMM_WIFI_AP_BLE) ? BLEDeviceName : "";
    return "N/A";
  }
  if (var == "BLE_CLIENTS") {
   //return (BTDeviceConnected) ? "1" : "0";
    return "N/A";
  }
  if (var == "AP_MAC") { return getSoftAPMacAddress(); }

  // Return an empty string if the variable is not recognized
  return var;
}



void handleUpdateProgress(AsyncWebServerRequest *request) {
    static const char* tag = "UpdateProgress";

    //if (!request->authenticate(http_username, http_password)) {
    //    return request->requestAuthentication();
    //}

    request->send(200, "text/plain", "Update progress"); // Send a simple response
}
