#include "ChronoWebServer.h"
#include <WebServer.h>
#include <ProjectileProfile.h>
#include <ChronoReadingManager.h>
#include <FileManager.h>
#include <ChronoProfile.h>
#include <GunProfile.h>
#include <Update.h>
#include "Display.h"
#include <nvs_flash.h>

static const char *SSID_KEY = "wifi_ssid";
static const char *PASSWORD_KEY = "wifi_password";
static Preferences preferences;

String getWifiSsid() {
    String ssid = "";
    if (preferences.begin("wifi", true)) {
        ssid = preferences.getString(SSID_KEY, "");
        preferences.end();
    }
    return ssid;
}

String getWifiPassword() {
    String password = "";
    if (preferences.begin("wifi", true)) {
        password = preferences.getString(PASSWORD_KEY, "");
        preferences.end();
    }
    return password;
}

void saveWifiCredentials(const String& ssid, const String& password) {
    preferences.begin("wifi", false);
    preferences.putString(SSID_KEY, ssid);
    preferences.putString(PASSWORD_KEY, password);
    preferences.end();
}

WebServer _server(80);
void (*testModeCallback)();

// Constructor implementation
ChronoWebServer::ChronoWebServer() {}
void ChronoWebServer::_handleRoot() {
    String temp="<html>\
  <head>\
    <title>FX Chronograph Display Settings</title>\
    <style>\
      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
      .clickable-row {cursor: pointer;}\
      .clickable-row:hover {background-color: #f0f0f0;}\
    </style>\
  </head>\
  <body>\
    <script>\
      function addGunProfile() {window.location.href = '/gunprofile';}\
      function addProjectileProfile() {window.location.href = '/projectileprofile';}\
      function setProfile(type,id){\
        const formData = new URLSearchParams();\
        formData.append('id', id);\
        var url='/projectileprofile/set';\
        if (type=='gun'){url='/gunprofile/set';}\
        fetch(url, {\
          method: 'POST',\
          body: formData\
        })\
        .then(response => response.text())\
        .then(data => {console.log(data); window.location.href = '/';})\
        .catch(error => console.error('Error:', error));\
      }\
      function exportData(){\
        const link = document.createElement('a');\
        link.href = '/export';\
        link.setAttribute('download', 'data.csv');\
        document.body.appendChild(link);\
        link.click();\
        document.body.removeChild(link);\
      }\
      function reset(){\
        fetch('/reset', {\
          method: 'GET',\
        })\
        .then(response => response.text())\
        .then(data => console.log(data))\
        .catch(error => console.error('Error:', error));\
      }\
      function format(){\
        fetch('/format', {\
          method: 'GET',\
        })\
        .then(response => response.text())\
        .then(data => console.log(data))\
        .catch(error => console.error('Error:', error));\
      }\
      function toggleTestMode(){\
        fetch('/testmode', {\
          method: 'GET',\
        })\
        .then(response => response.text())\
        .then(data => console.log(data))\
        .catch(error => console.error('Error:', error));\
      }\
      fetch('/version').then(r=>r.json()).then(v=>{document.getElementById('currentVersion').textContent=v.version}).catch(()=>{});\
      function doOta(){\
        const fileInput = document.getElementById('otaFile');\
        if (!fileInput.files.length) {\
          alert('Please select a firmware file');\
          return;\
        }\
        const formData = new FormData();\
        formData.append('otaFile', fileInput.files[0]);\
        const xhr = new XMLHttpRequest();\
        xhr.open('POST', '/ota', true);\
        xhr.onload = function () {\
          if (xhr.status >= 200 && xhr.status < 300) {\
            const responseData = JSON.parse(xhr.responseText);\
            console.log('Success:', responseData);\
            window.location.href = '/';\
          } else {\
            console.error('Server error:', xhr.status);\
          }\
        };\
        xhr.onerror = function () {\
          console.error('Network request failed');\
        };\
        xhr.send(formData);\
      }\
      document.addEventListener('DOMContentLoaded', function() {\
            const rows = document.querySelectorAll('.clickable-row');\
            rows.forEach(row => {\
                row.addEventListener('click', function() {\
                      const type = this.dataset.type;\
                      const id = this.dataset.id;\
                      if (id) {\
                          setProfile(type,id);\
                      }\
                });\
            });\
        const removeButtons = document.querySelectorAll('.remove-button');\
        removeButtons.forEach(button => {\
          button.addEventListener('click', (event) => {\
            event.stopPropagation();\
            const type = event.target.dataset.type;\
            const id = event.target.dataset.id;\
            var url='/projectileprofile/remove';\
            if (type === 'gun') {\
                url='/gunprofile/remove';\
            }\
            const formData = new URLSearchParams();\
            formData.append('id', id);\
            fetch(url, {\
              method: 'POST',\
              body: formData\
            })\
            .then(response => response.text())\
            .then(data => {console.log(data); window.location.href = '/';})\
            .catch(error => console.error('Error:', error));\
          });\
        });\
      });\
      function saveWifi(){\
        const ssid = document.getElementById('wifiSsidInput').value;\
        const password = document.getElementById('wifiPasswordInput').value;\
        if (!ssid) {\
          alert('Please enter a WiFi SSID');\
          return;\
        }\
        const formData = new URLSearchParams();\
        formData.append('ssid', ssid);\
        formData.append('password', password);\
        fetch('/wifisave', {\
          method: 'POST',\
          body: formData\
        })\
        .then(response => response.text())\
        .then(data => {\
          console.log(data);\
          alert('WiFi settings saved! Device will restart to connect.');\
          window.location.href = '/';\
        })\
        .catch(error => console.error('Error:', error));\
      }\
      function clearWifi(){\
        if(confirm('Clear saved WiFi credentials? Device will start its own AP after reboot.')){\
          fetch('/clearwifi', {\
            method: 'POST',\
            body: new URLSearchParams()\
          })\
          .then(response => response.text())\
          .then(data => {\
            console.log(data);\
            alert('WiFi credentials cleared. Device will restart.');\
          })\
          .catch(error => console.error('Error:', error));\
        }\
      }\
      fetch('/version').then(r=>r.json()).then(v=>{document.getElementById('currentVersion').textContent=v.version}).catch(()=>{});\
      fetch('/wifi').then(r=>r.text()).then(ssid=>{document.getElementById('wifiSsid').textContent=ssid || 'Not configured'}).catch(()=>{});\
      fetch('/wifimode').then(r=>r.text()).then(mode=>{\
        const modeEl = document.getElementById('wifiMode');\
        if (mode === 'AP') {\
          modeEl.textContent = 'Access Point (AP) Mode';\
          document.getElementById('wifiFormContainer').style.display = 'block';\
        } else if (mode === 'STA') {\
          modeEl.textContent = 'Station (STA) Mode - Connected to WiFi';\
          document.getElementById('wifiFormContainer').style.display = 'block';\
        } else {\
          modeEl.textContent = 'Unknown Mode';\
        }\
      }).catch(()=>{});\
    </script>\
    <h1>ChronoDisplay Settings</h1>\
    <div style='margin-top:50px; position:relative;'>\
      <h3>Control Panel</h3>\
      <button onclick='exportData()'>Export Data</button>\
      <button onclick='reset()'>Reset Data</button>\
      <button onclick='format()'>Delete Profiles</button>\
      <button onclick='toggleTestMode()'>Toggle Test Mode</button>\
    </div>\
    <div style='margin-top:20px; padding: 10px; border: 1px solid #999;'>\
      <h3>WiFi Configuration</h3>\
      <p style='font-size: 12px; color: #666;'>Mode: <strong id='wifiMode'>Unknown</strong></p>\
      <p style='font-size: 12px; color: #666;'>Current WiFi: <strong id='wifiSsid'>Not configured</strong></p>\
      <div id='wifiFormContainer' style='margin-top: 10px;'>\
        <form id='wifiForm'>\
          <label for='wifiSsidInput'>SSID:</label><br/>\
          <input type='text' id='wifiSsidInput' style='width: 200px; margin-bottom: 10px;'></input><br/>\
          <label for='wifiPasswordInput'>Password:</label><br/>\
          <input type='password' id='wifiPasswordInput' style='width: 200px; margin-bottom: 10px;'></input><br/>\
          <button type='button' onclick='saveWifi()' style='margin-top: 5px; padding: 8px 16px; background-color: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer;'>Save WiFi</button>\
        </form>\
        <p style='font-size: 12px; color: #666;'>After saving, the device will attempt to connect to the new WiFi network.</p>\
        <button type='button' onclick='clearWifi()' style='margin-top: 10px; padding: 8px 16px; background-color: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer;'>Clear WiFi Credentials</button>\
      </div>\
    </div>\
    <div style='margin-top:20px; padding: 10px; border: 1px solid #999;'>\
      <h3>OTA Firmware Update</h3>\
      <p style='font-size: 12px; color: #666;'>Current Version: <strong id='currentVersion'>Unknown</strong></p>\
      <input type='file' id='otaFile' accept='.bin,.bin' style='margin-bottom: 10px;'></input>\
      <p style='font-size: 12px; color: #666;'>1. Select a .bin firmware file to update over-the-air</p>\
      <button onclick='doOta()' style='margin-top: 5px; padding: 8px 16px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;'>OTA Upgrade</button>\
      <p style='font-size: 12px; color: #666;'>2. Click OTA Upgrade to install</p>\
    </div>\
    <div style='margin-top:100px; position:relative;'>\
        <h3>Gun Profiles</h3>\
        <table style='width:600px; min-height:100px; border-collapse: collapse;'>\
        <tr style='border-bottom:1px solid black;'><th>Name</th><th>Type</th><th></th></tr>";
  ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance();
  
  String currentGunName=chronoReadings.getProfile().getGunProfile().getName();
  int currentGunPowerLevel=chronoReadings.getProfile().getGunProfile().getPowerLevel();
  LinkedList<GunProfile>& gprofiles= chronoReadings.getGunProfiles();
  for(int i=0;i<gprofiles.size();i++){
    String name=gprofiles.get(i).getName();
    String type="";
    int powerlevel=gprofiles.get(i).getPowerLevel();
    if (powerlevel==0){type="Bow or Airsoft";}
    else if (powerlevel==1){type="CO2 Pistol";}
    else if (powerlevel==2){type="Air Pistol";}
    else if (powerlevel==3){type="Air Gun UK";}
    else if (powerlevel==4){type="FAC";}
    if (strcmp(name.c_str(),currentGunName.c_str())==0 && currentGunPowerLevel==powerlevel){
      temp +="<tr class='clickable-row' data-type='gun' data-id='"+String(i)+"' style='background-color:#b0b0b0;'>";
    }
    else{
      temp +="<tr class='clickable-row' data-type='gun' data-id='"+String(i)+"'>";
    }
    temp +="<td>"+name+"</td><td>"+type+"</td><td><button class='remove-button' data-type='gun' data-id='"+String(i)+"'>Remove</button></td></tr>";
  }

temp +="</table>\
        <button onclick='addGunProfile()' style='position:absolute; top:10px; left:575px;'>Add</button>\
    </div>\
    <div style='margin-top:100px; position:relative;'>\
        <h3>Projectile Profiles</h3>\
        <table style='width:600px; min-height:100px; border-collapse: collapse;'>\
        <tr style='border-bottom:1px solid black;'><th>Name</th><th>Type</th><th>Caliber</th><th>Weight</th><th></th></tr>";
  String currentProjectileProfileName=chronoReadings.getProfile().getProjectileProfile().getName();
  float currentProjectileProfileWeight=chronoReadings.getProfile().getProjectileProfile().getWeight();
  LinkedList<ProjectileProfile>& profiles= chronoReadings.getProjectileProfiles();
  for(int i=0;i<profiles.size();i++){
    String name=profiles.get(i).getName();
    String type=profiles.get(i).getType();
    String cal=String(profiles.get(i).getCaliber());
    float fweight=profiles.get(i).getWeight();
    String weight=String(fweight);
    if (strcmp(name.c_str(),currentProjectileProfileName.c_str())==0 && fweight==currentProjectileProfileWeight){
      temp +="<tr class='clickable-row' data-type='projectile' data-id='"+String(i)+"' style='background-color:#b0b0b0;'>";
    }
    else{
      temp +="<tr class='clickable-row' data-type='projectile' data-id='"+String(i)+"'>";
    }
    temp +="<td>"+name+"</td><td>"+type+"</td><td>"+cal+"</td><td>"+weight+"</td><td><button class='remove-button' data-type='projectile' data-id='"+String(i)+"'>Remove</button></td></tr>";
  }


        temp +="</table>\
        <button onclick='addProjectileProfile()' style='position:absolute; top:10px; left:575px;'>Add</button>\
    </div>\
  </body>\
</html>";
  _server.send(200, "text/html", temp);
}
void ChronoWebServer::_handleGunProfile(){
  String temp="<html>\
  <head>\
    <title>Gun Profile</title>\
    <style>\
      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
    </style>\
  </head>\
  <body>\
  <script>\
      function addProfile() {\
        var name=document.getElementById('name').value;\
        var type=document.getElementById('type').value;\
        const formData = new URLSearchParams();\
        formData.append('name', name);\
        formData.append('type', type);\
        fetch('/gunprofile/add', {\
          method: 'POST',\
          body: formData\
        })\
        .then(response => response.text())\
        .then(data => {console.log(data); window.location.href = '/';})\
        .catch(error => console.error('Error:', error));\
      }\
    </script>\
  <h1>Add Gun Profile</h1>\
  <div>\
    <h3>Name</h3>\
    <input id='name' style='width:200px;'></input>\
    <h3>Type</h3>\
    <select id='type'>\
    <option value='0'>Bow or Airsoft</option>\
    <option value='1'>CO2 Pistol</option>\
    <option value='2'>Air Pistol</option>\
    <option value='3'>Air Gun UK</option>\
    <option value='4'>FAC</option>\
    </select>\
    <div style='margin-top:50px;'><button onclick='addProfile()'>Add Profile</button></div>\
  </div>\
  </body>\
  </html>";
  _server.send(200, "text/html", temp);
}
void ChronoWebServer::_handleAddGunProfile(){
  if (_server.method() == HTTP_POST){
    String name="Default";
    GunProfile::GunProfileType type=GunProfile::PROFILE_TYPE_AIR_GUN_FAC;
    float caliber=0.0;
    float weight=0.0;
    if (_server.hasArg("name")){name=_server.arg("name");}
    if (_server.hasArg("type")){
      String st=_server.arg("type");
      if (strcmp(st.c_str(),"0")==0){type=GunProfile::PROFILE_TYPE_BOW_AIRSOFT;}
      else if (strcmp(st.c_str(),"1")==0){type=GunProfile::PROFILE_TYPE_CO2_PISTOL;}
      else if (strcmp(st.c_str(),"2")==0){type=GunProfile::PROFILE_TYPE_AIR_PISTOL;}
      else if (strcmp(st.c_str(),"3")==0){type=GunProfile::PROFILE_TYPE_AIR_GUN_UK;}
      else if (strcmp(st.c_str(),"4")==0){type=GunProfile::PROFILE_TYPE_AIR_GUN_FAC;}
    }
    ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance();
    GunProfile p(name,type);
    chronoReadings.addGunProfile(p);
  }

  _server.send(200, "text/html", "ok");
}
void ChronoWebServer::_handleSetGunProfile(){
  if (_server.method() == HTTP_POST){
    if (_server.hasArg("id")){
      int id=_server.arg("id").toInt();
      ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance();
      chronoReadings.setGunProfile(id);

      //update profile with chronograph
      int type=chronoReadings.getProfile().getGunProfile().getPowerLevel();
      ChronoBLE::changeGunType(type);

      //update display
      Display::updateProfile(chronoReadings.getProfile());
      Display::refreshDisplay();
    }
  }
  _server.send(200, "text/html", "ok");
}
void ChronoWebServer::_handleRemoveGunProfile(){
  if (_server.method() == HTTP_POST){
    if (_server.hasArg("id")){
      int id=_server.arg("id").toInt();
      ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance();
      chronoReadings.removeGunProfile(id);
    }
  }
  _server.send(200, "text/html", "ok");
}
void ChronoWebServer::_handleProjectileProfile(){
  String temp="<html>\
  <head>\
    <title>Projectile Profile</title>\
    <style>\
      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
    </style>\
  </head>\
  <body>\
  <script>\
      function addProfile() {\
        var name=document.getElementById('name').value;\
        var type=document.getElementById('type').value;\
        var cal=document.getElementById('caliber').value;\
        var weight=document.getElementById('weight').value;\
        const formData = new URLSearchParams();\
        formData.append('name', name);\
        formData.append('type', type);\
        formData.append('cal', cal);\
        formData.append('weight', weight);\
        fetch('/projectileprofile/add', {\
          method: 'POST',\
          body: formData\
        })\
        .then(response => response.text())\
        .then(data => {console.log(data); window.location.href = '/';})\
        .catch(error => console.error('Error:', error));\
      }\
    </script>\
  <h1>Add Projectile Profile</h1>\
  <div>\
    <h3>Name</h3>\
    <input id='name' style='width:200px;'></input>\
    <h3>Type</h3>\
    <select id='type'>\
    <option value='pellet'>Pellet</option>\
    <option value='slug'>Slug</option>\
    </select>\
    <h3>Caliber</h3>\
    <input id='caliber' style='width:50px;'></input>\
    <h3>Weight</h3>\
    <input id='weight' style='width:50px;'></input>\
    <div style='margin-top:50px;'><button onclick='addProfile()'>Add Profile</button></div>\
  </div>\
  </body>\
  </html>";
  _server.send(200, "text/html", temp);
}
void ChronoWebServer::_handleSetProjectileProfile(){
  if (_server.method() == HTTP_POST){
    if (_server.hasArg("id")){
      int id=_server.arg("id").toInt();
      ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance();
      chronoReadings.setProjectileProfile(id);
      Display::updateProfile(chronoReadings.getProfile());
      Display::refreshDisplay();
    }
  }
  _server.send(200, "text/html", "ok");
}
void ChronoWebServer::_handleAddProjectileProfile(){
  String message = "Got request...\n\n";
  message += "URI: ";
  message += _server.uri();
  message += "\nMethod: ";
  message += (_server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += _server.args();
  message += "\n";
  
  for (uint8_t i = 0; i < _server.args(); i++) {
    message += " " + _server.argName(i) + ": " + _server.arg(i) + "\n";
  }
  Serial.println(message);

  if (_server.method() == HTTP_POST){
    String name="";
    ProjectileProfile::ProjectileType type=ProjectileProfile::PELLET;
    float caliber=0.0;
    float weight=0.0;
    if (_server.hasArg("name")){name=_server.arg("name");}
    if (_server.hasArg("type")){
      String st=_server.arg("type");
      if (strcmp(st.c_str(),"slug")==0){
        type=ProjectileProfile::SLUG;
      }
    }
    if (_server.hasArg("cal")){caliber=_server.arg("cal").toFloat();}
    if (_server.hasArg("weight")){weight=_server.arg("weight").toFloat();}
    ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance();
    ProjectileProfile pp(name,type,caliber,weight);
    chronoReadings.addProjectileProfile(pp);
  }

  _server.send(200, "text/html", "ok");
}
void ChronoWebServer::_handleRemoveProjectileProfile(){
  if (_server.method() == HTTP_POST){
    if (_server.hasArg("id")){
      int id=_server.arg("id").toInt();
      ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance();
      chronoReadings.removeProjectileProfile(id);
    }
  }
  _server.send(200, "text/html", "ok");
}
void ChronoWebServer::_handleExport(){
  
  ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance();
  LinkedList<ChronoReading>& readings=chronoReadings.getChronographReadings();
  int count=readings.size();
  Serial.print("exporting ");
  Serial.print(count);
  Serial.println(" readings to csv...");

  //return csv data
  String csvData = "Shot #,FPS,FPE,Gun,Power,Projectile,Type,Caliber,Weight\n";
  for (int i=0; i<count;i++){
    ChronoReading r=readings.get(i);
    String shotNum=String(i+1);
    String fps=String(r.getFPS());
    String fpe=String(r.getFPE());
    String gun=r.getProfile().getGunProfile().getName();
    String power=String(r.getProfile().getGunProfile().getPowerLevel());
    String projectile=r.getProfile().getProjectileProfile().getName();
    String ptype=r.getProfile().getProjectileProfile().getType();
    String cal=String(r.getProfile().getProjectileProfile().getCaliber());
    String weight=String(r.getProfile().getProjectileProfile().getWeight());
    csvData += shotNum + ",";
    csvData += fps + ",";
    csvData += fpe + ",";
    csvData += gun + ",";
    csvData += power + ",";
    csvData += projectile + ",";
    csvData += ptype + ",";
    csvData += cal + ",";
    csvData += weight + "\n";
  }

  _server.sendHeader("Content-Disposition", "attachment; filename=data.csv");
  _server.send(200, "text/csv", csvData);


  //_server.send(200, "text/html", "ok");
}
void ChronoWebServer::_handleReset(){
  ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance();
  chronoReadings.reset();
  Display::updateEntireDisplay();
  Display::refreshDisplay();
  _server.send(200, "text/html", "ok");
}
void ChronoWebServer::_handleFormat(){
  Serial.println("formating disk, erasing all data...");
  FileManager& files = FileManager::getInstance();
  files.formatDisk();
  _server.send(200, "text/html", "ok");
}
void ChronoWebServer::setTestModeCallback(void (*pFunc)()){
  testModeCallback=pFunc;
}
void ChronoWebServer::_handleTestMode(){
  Serial.println("toggling test mode....");
  testModeCallback();
  _server.send(200, "text/html", "ok");
}
void handleOtaUpdate() {
  size_t fsize = UPDATE_SIZE_UNKNOWN;
  if (_server.hasArg("size")) {
    fsize = _server.arg("size").toInt();
  }
  HTTPUpload &upload = _server.upload();
  if (upload.status == UPLOAD_FILE_START) {
   
    Serial.printf("Receiving Update: %s, Size: %d\n", upload.filename.c_str(), fsize);
    if (!Update.begin(fsize)) {
      //otaDone = 0;
      //Update.printError(Serial);
    }
  } else if (upload.status == UPLOAD_FILE_WRITE) {
    if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
    //  Update.printError(Serial);
    } else {
    //  otaDone = 100 * Update.progress() / Update.size();
    }
  } else if (upload.status == UPLOAD_FILE_END) {
    if (Update.end(true)) {
      Serial.printf("Update Success: %u bytes\nRebooting...\n", upload.totalSize);
    } else {
      Serial.printf("%s\n", Update.errorString());
     // otaDone = 0;
    }
  }
}
void handleOtaUpdateEnd() {
  _server.sendHeader("Connection", "close");
  if (Update.hasError()) {
    _server.send(502, "text/plain", Update.errorString());
  } else {
    _server.sendHeader("Refresh", "10");
    _server.sendHeader("Location", "/");
    _server.send(307);
    delay(500);
    ESP.restart();
  }
}
void setupSoftAp(const char* ssid, const char* password) {

  WiFi.mode(WIFI_AP);
  IPAddress local_ip(192, 168, 4, 1);
  IPAddress gateway(192, 168, 4, 1);
  IPAddress subnet(255, 255, 255, 0);

  WiFi.softAPConfig(local_ip, gateway, subnet);
  delay(100);
  
  WiFi.softAP(ssid, password);//, password);
  delay(100);

  Serial.print("AP started: ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.softAPIP());
}

bool attemptStaConnection(const String& ssid, const String& password, int timeoutMs) {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid.c_str(), password.c_str());

  int attempts = 0;
  int maxAttempts = timeoutMs / 500;

  while (WiFi.status() != WL_CONNECTED && attempts < maxAttempts) {
    delay(500);
    Serial.print(".");
    attempts++;
  }

  return WiFi.status() == WL_CONNECTED;
}

void ChronoWebServer::init(){
  // Initialize NVS flash and erase WiFi config partition
  esp_err_t err = nvs_flash_init();
  if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    Serial.println("NVS partition was truncated, erasing...");
    ESP_ERROR_CHECK(nvs_flash_erase());
    err = nvs_flash_init();
  }
  ESP_ERROR_CHECK(err);

  // Erase WiFi storage in NVS
  nvs_handle_t handle;
  err = nvs_open("storage", NVS_READWRITE, &handle);
  if (err == ESP_OK) {
    nvs_erase_all(handle);
    nvs_commit(handle);
    nvs_close(handle);
    Serial.println("WiFi NVS storage erased");
  } else {
    Serial.println("No WiFi NVS storage to erase");
  }

  WiFi.disconnect(true, true);
  WiFi.mode(WIFI_OFF);
  delay(100);

  // Try to load saved WiFi credentials
  String savedSsid = getWifiSsid();
  String savedPassword = getWifiPassword();

  Serial.print("Saved SSID from Preferences: '");
  Serial.print(savedSsid);
  Serial.println("'");
  Serial.print("Saved Password length: ");
  Serial.println(savedPassword.length());

  bool connected = false;

  if (savedSsid.length() > 0) {
    Serial.print("Attempting to connect to saved WiFi: ");
    Serial.println(savedSsid);
    connected = attemptStaConnection(savedSsid, savedPassword, 7000);

    if (connected) {
      Serial.println("");
      Serial.print("Connected to ");
      Serial.println(savedSsid);
      Serial.print("IP address: ");
      Serial.println(WiFi.localIP());
    } else {
      Serial.println("");
      Serial.println("Failed to connect to saved WiFi - starting AP mode");
      WiFi.disconnect(true, true);
      WiFi.mode(WIFI_OFF);
      delay(100);
    }
  } else {
    Serial.println("No saved WiFi credentials - starting AP mode");
  }

  if (!connected) {
    setupSoftAp("ChronoDisplay", "12345678");

    if (MDNS.begin("esp32")) {
      Serial.println("MDNS responder started");
    }
  }

  _server.on("/gunprofile/add", _handleAddGunProfile);
  _server.on("/gunprofile/set", _handleSetGunProfile);
  _server.on("/gunprofile/remove", _handleRemoveGunProfile);
  _server.on("/gunprofile", _handleGunProfile);
  _server.on("/projectileprofile/set", _handleSetProjectileProfile);
  _server.on("/projectileprofile/add", _handleAddProjectileProfile);
  _server.on("/projectileprofile/remove", _handleRemoveProjectileProfile);
  _server.on("/projectileprofile", _handleProjectileProfile);
  _server.on("/export", _handleExport);
  _server.on("/reset", _handleReset);
  _server.on("/format", _handleFormat);
  _server.on("/testmode", _handleTestMode);
  _server.on("/ota", HTTP_POST, [](){handleOtaUpdateEnd();}, [](){handleOtaUpdate();});
  _server.on("/wifi", HTTP_GET, _handleWifi);
  _server.on("/wifisave", HTTP_POST, _handleWifiSave);
  _server.on("/clearwifi", HTTP_POST, _handleClearWifi);
  _server.on("/wifimode", HTTP_GET, _handleWifiMode);
  _server.on("/version", HTTP_GET, _handleVersion);
  _server.on("/", _handleRoot);
  _server.begin();

}

void ChronoWebServer::process(){
  _server.handleClient();
}

void ChronoWebServer::_handleVersion() {
  String versionJson = String("{\"version\":\"") + CHRONO_VERSION + "\",\"major\":" + String(CHRONO_VERSION_MAJOR) + ",\"minor\":" + String(CHRONO_VERSION_MINOR) + ",\"patch\":" + String(CHRONO_VERSION_PATCH) + "}";
  _server.send(200, "application/json", versionJson);
}

void ChronoWebServer::_handleWifi() {
    String ssid = getWifiSsid();
    _server.send(200, "text/plain", ssid);
}

void ChronoWebServer::_handleWifiSave() {
    if (_server.method() == HTTP_POST) {
        String ssid = "";
        String password = "";
        if (_server.hasArg("ssid")) {
            ssid = _server.arg("ssid");
        }
        if (_server.hasArg("password")) {
            password = _server.arg("password");
        }
        saveWifiCredentials(ssid, password);
        _server.send(200, "text/html", "ok");
        delay(1000);
        ESP.restart();
    }
}

void ChronoWebServer::_handleClearWifi() {
    if (_server.method() == HTTP_POST) {
        preferences.begin("wifi", false);
        preferences.clear();
        preferences.end();
        _server.send(200, "text/html", "ok");
        delay(1000);
        ESP.restart();
    }
}

void ChronoWebServer::_handleWifiMode() {
    String mode;
    if (WiFi.getMode() == WIFI_STA) {
        mode = "STA";
    } else if (WiFi.getMode() == WIFI_AP) {
        mode = "AP";
    } else if (WiFi.getMode() == WIFI_AP_STA) {
        mode = "AP+STA";
    } else {
        mode = "NONE";
    }
    _server.send(200, "text/plain", mode);
}