#include <ESPAsyncWebServer.h>
#include <AsyncJson.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <Preferences.h>
#include <time.h>
#include "config.h"
#include "state.h"
#include "webserver.h"
#include "rules.h"
#include "sleep.h"
#include "historique.h"
#include "modbus_epever.h"
#include "epever_config.h"
#include "wifi_ap.h"
#include "wireguard_vpn.h"
#include "debug_log.h"

AsyncWebServer server(80);

// --- Persistance relais (NVS — survit au power-off) ---

static void sauvegarderRelaisNVS() {
    Preferences prefs;
    prefs.begin("relais", false);
    prefs.putBool("r1", state.relay1);
    prefs.putBool("r2", state.relay2);
    prefs.end();
    Serial.printf("[NVS] Relais sauvegardés — R1:%d R2:%d\n", state.relay1, state.relay2);
}

void restaurerRelaisNVS() {
    Preferences prefs;
    prefs.begin("relais", true);
    state.relay1 = prefs.getBool("r1", false);
    state.relay2 = prefs.getBool("r2", false);
    prefs.end();
    digitalWrite(PIN_RELAY1, state.relay1 ? HIGH : LOW);
    digitalWrite(PIN_RELAY2, state.relay2 ? HIGH : LOW);
    Serial.printf("[NVS] Relais restaurés — R1:%d R2:%d\n", state.relay1, state.relay2);
}

// Sérialise l'état système en JSON et répond à la requête
static void envoyerEtat(AsyncWebServerRequest *request) {
    JsonDocument doc;
    // PV
    doc["pv"]          = state.pv;
    doc["pvCurrent"]   = state.pvCurrent;
    // Batterie
    doc["battery"]         = state.battery;
    doc["batSOC"]          = state.batSOC;
    doc["batTemperature"]  = state.batTemperature;
    doc["batStatut"]       = state.batStatut;
    doc["batSousVoltage"]  = state.batSousVoltage;
    doc["batSurVoltage"]   = state.batSurVoltage;
    // Load
    doc["loadVoltage"] = state.loadVoltage;
    doc["loadCurrent"] = state.loadCurrent;
    doc["loadPower"]   = state.loadPower;
    // Énergie kWh
    doc["energieGenJour"]  = state.energieGenJour;
    doc["energieGenTotal"] = state.energieGenTotal;
    doc["energieConJour"]  = state.energieConJour;
    doc["energieConTotal"] = state.energieConTotal;
    // Général
    doc["sun"]         = state.sun;
    doc["espClockOk"]  = state.espClockOk;
    if (state.espClockOk) {
        time_t now = time(nullptr);
        struct tm tmNow;
        localtime_r(&now, &tmNow);
        char espTime[20];
        snprintf(espTime, sizeof(espTime), "%04d-%02d-%02d %02d:%02d:%02d",
                 tmNow.tm_year + 1900, tmNow.tm_mon + 1, tmNow.tm_mday,
                 tmNow.tm_hour, tmNow.tm_min, tmNow.tm_sec);
        doc["espTime"] = espTime;
    } else {
        doc["espTime"] = "--";
    }
    doc["epeverClockOk"] = state.epeverClockOk;
    if (state.epeverClockOk) {
        char rtc[20];
        snprintf(rtc, sizeof(rtc), "%04u-%02u-%02u %02u:%02u:%02u",
                 state.epeverYear, state.epeverMonth, state.epeverDay,
                 state.epeverHour, state.epeverMinute, state.epeverSecond);
        doc["epeverTime"] = rtc;
    } else {
        doc["epeverTime"] = "--";
    }
    doc["relay1"]      = state.relay1;
    doc["relay2"]      = state.relay2;
    doc["di1"]         = state.di1;
    doc["di2"]         = state.di2;
    doc["autoMode"]    = state.autoMode;
    doc["rs485_ok"]    = state.rs485_ok;
    doc["last_update"] = state.last_update;

    String json;
    serializeJson(doc, json);
    request->send(200, "application/json", json);
}

// Commande un relais et met à jour l'état global
static void commanderRelais(AsyncWebServerRequest *request, int relais, bool etat) {
    if (relais == 1) {
        state.relay1 = etat;
        digitalWrite(PIN_RELAY1, etat ? HIGH : LOW);
    } else if (relais == 2) {
        state.relay2 = etat;
        digitalWrite(PIN_RELAY2, etat ? HIGH : LOW);
    }
    Serial.printf("[WEB] Relais %d → %s  (client %s)\n",
                  relais, etat ? "ON" : "OFF",
                  request->client()->remoteIP().toString().c_str());
    request->send(200, "application/json", "{\"ok\":true}");
}

void demarrerWebserveur() {
    if (!LittleFS.begin(true)) {
        Serial.println("Erreur : impossible de monter LittleFS");
        return;
    }
    Serial.println("LittleFS monté");

    // --- API REST (définie avant le handler statique) ---

    server.on("/api/state", HTTP_GET, envoyerEtat);

    server.on("/api/debug/logs", HTTP_GET, [](AsyncWebServerRequest *r) {
        String json;
        getDebugLogJson(json);
        r->send(200, "application/json", json);
    });

    server.on("/api/debug/clear", HTTP_POST, [](AsyncWebServerRequest *r) {
        clearDebugLog();
        r->send(200, "application/json", "{\"ok\":true}");
    });

    server.on("/api/sun/history", HTTP_GET, [](AsyncWebServerRequest *r) {
        JsonDocument doc;
        JsonArray arr = doc["changes"].to<JsonArray>();
        for (uint8_t i = 0; i < state.sunHistoryCount; i++) {
            uint8_t idx = (state.sunHistoryHead + 5 - state.sunHistoryCount + i) % 5;
            JsonObject item = arr.add<JsonObject>();
            item["sun"] = state.sunHistoryState[idx];
            item["label"] = state.sunHistoryState[idx] ? "Jour" : "Nuit";
            item["time"] = state.sunHistoryTime[idx];
        }
        String json;
        serializeJson(doc, json);
        r->send(200, "application/json", json);
    });

    server.on("/api/relay/1/on",  HTTP_POST, [](AsyncWebServerRequest *r){ commanderRelais(r, 1, true);  });
    server.on("/api/relay/1/off", HTTP_POST, [](AsyncWebServerRequest *r){ commanderRelais(r, 1, false); });
    server.on("/api/relay/2/on",  HTTP_POST, [](AsyncWebServerRequest *r){ commanderRelais(r, 2, true);  });
    server.on("/api/relay/2/off", HTTP_POST, [](AsyncWebServerRequest *r){ commanderRelais(r, 2, false); });

    // Toggle + sauvegarde NVS (appui long dashboard)
    server.on("/api/relay/1/toggle", HTTP_POST, [](AsyncWebServerRequest *r){
        state.relay1 = !state.relay1;
        digitalWrite(PIN_RELAY1, state.relay1 ? HIGH : LOW);
        sauvegarderRelaisNVS();
        Serial.printf("[WEB] Relais 1 toggle → %s (NVS sauvegardé)\n", state.relay1 ? "ON" : "OFF");
        r->send(200, "application/json", "{\"ok\":true}");
    });
    server.on("/api/relay/2/toggle", HTTP_POST, [](AsyncWebServerRequest *r){
        state.relay2 = !state.relay2;
        digitalWrite(PIN_RELAY2, state.relay2 ? HIGH : LOW);
        sauvegarderRelaisNVS();
        Serial.printf("[WEB] Relais 2 toggle → %s (NVS sauvegardé)\n", state.relay2 ? "ON" : "OFF");
        r->send(200, "application/json", "{\"ok\":true}");
    });

    server.on("/api/reboot", HTTP_POST, [](AsyncWebServerRequest *r){
        Serial.println("[WEB] Reboot demandé");
        r->send(200, "application/json", "{\"ok\":true}");
        delay(200);
        ESP.restart();
    });

    auto *handlerEpeverTime = new AsyncCallbackJsonWebHandler("/api/epever/time",
        [](AsyncWebServerRequest *r, JsonVariant &json) {
            JsonObject obj = json.as<JsonObject>();
            uint16_t year = obj["year"] | 0;
            uint8_t month = obj["month"] | 0;
            uint8_t day = obj["day"] | 0;
            uint8_t hour = obj["hour"] | 0;
            uint8_t minute = obj["minute"] | 0;
            uint8_t second = obj["second"] | 0;
            bool ok = reglerHorlogeEpever(year, month, day, hour, minute, second);
            r->send(ok ? 200 : 409, "application/json", ok ? "{\"ok\":true}" : "{\"ok\":false}");
        });
    server.addHandler(handlerEpeverTime);

    // --- API règles ---

    server.on("/api/rules", HTTP_GET, [](AsyncWebServerRequest *r) {
        JsonDocument doc;
        reglesToJson(doc.to<JsonArray>());
        String json;
        serializeJson(doc, json);
        r->send(200, "application/json", json);
    });

    server.on("/api/rules/toggle", HTTP_POST, [](AsyncWebServerRequest *r) {
        if (!r->hasParam("id")) { r->send(400); return; }
        int id = r->getParam("id")->value().toInt();
        bool ok = toggleRegle(id);
        Serial.printf("[WEB] Règle %d toggle → %s\n", id, ok ? "ok" : "introuvable");
        r->send(ok ? 200 : 404, "application/json", "{\"ok\":true}");
    });

    server.on("/api/rules/delete", HTTP_POST, [](AsyncWebServerRequest *r) {
        if (!r->hasParam("id")) { r->send(400); return; }
        int id = r->getParam("id")->value().toInt();
        bool ok = supprimerRegle(id);
        Serial.printf("[WEB] Règle %d supprimée → %s\n", id, ok ? "ok" : "introuvable");
        r->send(ok ? 200 : 404, "application/json", "{\"ok\":true}");
    });

    // Ajout de règle — corps JSON
    auto *handlerRegle = new AsyncCallbackJsonWebHandler("/api/rules",
        [](AsyncWebServerRequest *r, JsonVariant &json) {
            bool ok = ajouterRegle(json.as<JsonObject>());
            Serial.printf("[WEB] Ajout règle → %s\n", ok ? "ok" : "erreur");
            r->send(ok ? 201 : 500, "application/json", ok ? "{\"ok\":true}" : "{\"ok\":false}");
        });
    server.addHandler(handlerRegle);

    // --- API historique ---

    server.on("/api/history", HTTP_GET, [](AsyncWebServerRequest *r) {
        String json;
        getHistoriqueJson(json);          // lores : 30h, 5 min
        r->send(200, "application/json", json);
    });
    server.on("/api/history/hires", HTTP_GET, [](AsyncWebServerRequest *r) {
        String json;
        getHistoriqueHiresJson(json);     // hires : 4h, 1 min
        r->send(200, "application/json", json);
    });
    server.on("/api/history/status", HTTP_GET, [](AsyncWebServerRequest *r) {
        String json;
        getHistoriqueStatusJson(json);
        r->send(200, "application/json", json);
    });

    server.on("/api/history/csv", HTTP_GET, [](AsyncWebServerRequest *r) {
        String csv;
        getHistoriqueCsv(csv);
        AsyncWebServerResponse *resp = r->beginResponse(200, "text/csv", csv);
        resp->addHeader("Content-Disposition", "attachment; filename=\"historique.csv\"");
        r->send(resp);
    });

    // --- API noms (relais / entrées) ---

    server.on("/api/names", HTTP_GET, [](AsyncWebServerRequest *r) {
        Preferences p; p.begin("noms", true);
        JsonDocument doc;
        doc["relay1"] = p.getString("r1", "Relais 1");
        doc["relay2"] = p.getString("r2", "Relais 2");
        doc["di1"]    = p.getString("d1", "Entrée 1");
        doc["di2"]    = p.getString("d2", "Entrée 2");
        p.end();
        String json; serializeJson(doc, json);
        r->send(200, "application/json", json);
    });

    auto *handlerNoms = new AsyncCallbackJsonWebHandler("/api/names",
        [](AsyncWebServerRequest *r, JsonVariant &json) {
            JsonObject obj = json.as<JsonObject>();
            Preferences p; p.begin("noms", false);
            if (obj["relay1"].is<const char*>()) p.putString("r1", obj["relay1"].as<const char*>());
            if (obj["relay2"].is<const char*>()) p.putString("r2", obj["relay2"].as<const char*>());
            if (obj["di1"].is<const char*>())    p.putString("d1", obj["di1"].as<const char*>());
            if (obj["di2"].is<const char*>())    p.putString("d2", obj["di2"].as<const char*>());
            p.end();
            Serial.println("[NVS] Noms sauvegardés");
            r->send(200, "application/json", "{\"ok\":true}");
        });
    server.addHandler(handlerNoms);

    // --- API sleep / config ---

    server.on("/api/sleep", HTTP_GET, [](AsyncWebServerRequest *r) {
        String json;
        getSleepConfigJson(json);
        r->send(200, "application/json", json);
    });

    auto *handlerSleep = new AsyncCallbackJsonWebHandler("/api/sleep",
        [](AsyncWebServerRequest *r, JsonVariant &json) {
            JsonObject obj = json.as<JsonObject>();
            bool actif     = obj["actif"]      | false;
            uint32_t inv   = obj["intervalle"] | 600u;
            float seuil    = obj["seuil"]      | 2.0f;
            bool ok = setSleepConfig(actif, inv, seuil);
            Serial.printf("[WEB] Sleep config — actif:%d intervalle:%ds seuil:%.1fV → %s\n",
                          actif, inv, seuil, ok ? "ok" : "erreur");
            r->send(ok ? 200 : 500, "application/json", ok ? "{\"ok\":true}" : "{\"ok\":false}");
        });
    server.addHandler(handlerSleep);

    // --- API configuration EPEVER ---

    // GET  /api/epever/config → lecture depuis l'Epever + retour JSON
    server.on("/api/epever/config", HTTP_GET, [](AsyncWebServerRequest *r) {
        String json;
        bool ok = lireConfigEpever();
        if (!ok) {
            r->send(503, "application/json", "{\"ok\":false,\"erreur\":\"RS485 indisponible ou Modbus occupe\"}");
            return;
        }
        getConfigJson(json);
        r->send(200, "application/json", json);
    });

    // POST /api/epever/config → écriture vers l'Epever
    auto *handlerConfigWrite = new AsyncCallbackJsonWebHandler("/api/epever/config",
        [](AsyncWebServerRequest *r, JsonVariant &json) {
            JsonObject obj = json.as<JsonObject>();
            String erreur;
            bool ok = ecrireConfigEpever(obj, erreur);
            if (ok) {
                r->send(200, "application/json", "{\"ok\":true}");
            } else {
                String body = "{\"ok\":false,\"erreur\":\"" + erreur + "\"}";
                r->send(409, "application/json", body);
            }
        });
    server.addHandler(handlerConfigWrite);

    // GET  /api/epever/config/saved → lecture sauvegarde LittleFS
    server.on("/api/epever/config/saved", HTTP_GET, [](AsyncWebServerRequest *r) {
        String json;
        getConfigSauvegardeJson(json);
        r->send(200, "application/json", json);
    });

    // POST /api/epever/config/save → sauvegarde cache → LittleFS
    server.on("/api/epever/config/save", HTTP_POST, [](AsyncWebServerRequest *r) {
        bool ok = sauvegarderConfigJson();
        r->send(ok ? 200 : 500, "application/json",
                ok ? "{\"ok\":true}" : "{\"ok\":false,\"erreur\":\"Cache vide ou LittleFS inaccessible\"}");
    });

    // POST /api/epever/config/restore → LittleFS → Epever
    server.on("/api/epever/config/restore", HTTP_POST, [](AsyncWebServerRequest *r) {
        String erreur;
        bool ok = restaurerConfigJson(erreur);
        if (ok) {
            r->send(200, "application/json", "{\"ok\":true}");
        } else {
            String body = "{\"ok\":false,\"erreur\":\"" + erreur + "\"}";
            r->send(409, "application/json", body);
        }
    });

    // --- API intervalles Modbus ---
    server.on("/api/modbus", HTTP_GET, [](AsyncWebServerRequest *r) {
        uint32_t jour, nuit;
        getIntervallesModbus(jour, nuit);
        JsonDocument doc;
        doc["jour"] = jour;
        doc["nuit"] = nuit;
        String json; serializeJson(doc, json);
        r->send(200, "application/json", json);
    });

    auto *handlerModbus = new AsyncCallbackJsonWebHandler("/api/modbus",
        [](AsyncWebServerRequest *r, JsonVariant &json) {
            JsonObject obj = json.as<JsonObject>();
            uint32_t jour = obj["jour"] | 5000u;
            uint32_t nuit = obj["nuit"] | 30000u;
            jour = constrain(jour,  1000u, 60000u);
            nuit = constrain(nuit, 5000u, 300000u);
            setIntervallesModbus(jour, nuit);
            r->send(200, "application/json", "{\"ok\":true}");
        });
    server.addHandler(handlerModbus);

    // --- API WiFi ---

    // Statut complet AP + STA
    server.on("/api/wifi/status", HTTP_GET, [](AsyncWebServerRequest *r) {
        String json;
        getWifiStatusJson(json);
        r->send(200, "application/json", json);
    });

    // Scan réseaux (synchrone ~3-5s)
    server.on("/api/wifi/scan", HTTP_GET, [](AsyncWebServerRequest *r) {
        String json;
        scannerReseauxJson(json);
        r->send(200, "application/json", json);
    });

    // Connexion à un réseau WiFi existant
    auto *handlerWifiSta = new AsyncCallbackJsonWebHandler("/api/wifi/sta",
        [](AsyncWebServerRequest *r, JsonVariant &json) {
            JsonObject obj = json.as<JsonObject>();
            const char *ssid = obj["ssid"] | "";
            const char *pass = obj["pass"] | "";
            if (!ssid || strlen(ssid) == 0) {
                r->send(400, "application/json", "{\"ok\":false,\"erreur\":\"SSID manquant\"}");
                return;
            }
            if (strlen(ssid) > 32 || strlen(pass) > 64) {
                r->send(400, "application/json", "{\"ok\":false,\"erreur\":\"SSID ou mot de passe trop long\"}");
                return;
            }
            connecterWifiSTA(ssid, pass);
            r->send(200, "application/json", "{\"ok\":true}");
        });
    server.addHandler(handlerWifiSta);

    // Oublier le réseau STA (retour AP seul)
    server.on("/api/wifi/sta/disconnect", HTTP_POST, [](AsyncWebServerRequest *r) {
        deconnecterWifiSTA();
        r->send(200, "application/json", "{\"ok\":true}");
    });

    // Hostname mDNS
    server.on("/api/mdns", HTTP_GET, [](AsyncWebServerRequest *r) {
        JsonDocument doc;
        doc["hostname"] = getMdnsHostname();
        String json; serializeJson(doc, json);
        r->send(200, "application/json", json);
    });

    auto *handlerMdns = new AsyncCallbackJsonWebHandler("/api/mdns",
        [](AsyncWebServerRequest *r, JsonVariant &json) {
            const char *nom = json["hostname"] | "";
            bool ok = setMdnsHostname(nom);
            r->send(ok ? 200 : 400, "application/json",
                    ok ? "{\"ok\":true}" : "{\"ok\":false,\"erreur\":\"Nom invalide (alphanum + tirets, 1-63 car.)\"}");
        });
    server.addHandler(handlerMdns);

    // --- API WireGuard ---

    server.on("/api/wireguard", HTTP_GET, [](AsyncWebServerRequest *r) {
        String json;
        getWireGuardJson(json);
        r->send(200, "application/json", json);
    });

    auto *handlerWg = new AsyncCallbackJsonWebHandler("/api/wireguard",
        [](AsyncWebServerRequest *r, JsonVariant &json) {
            JsonObject obj  = json.as<JsonObject>();
            bool enabled    = obj["enabled"]   | false;
            const char* prv = obj["privkey"]   | "";
            const char* pub = obj["pubkey"]    | "";
            const char* psk = obj["psk"]       | "";
            const char* ep  = obj["endpoint"]  | "";
            uint16_t port   = obj["port"]      | 51820u;
            const char* ip  = obj["localip"]   | "";
            uint16_t ka     = obj["keepalive"] | 25u;
            if (!prv[0] || !pub[0] || !ep[0] || !ip[0]) {
                r->send(400, "application/json", "{\"ok\":false,\"erreur\":\"Champs obligatoires manquants\"}");
                return;
            }
            bool ok = setWireGuardConfig(enabled, prv, pub, psk, ep, port, ip, ka);
            r->send(ok ? 200 : 500, "application/json", ok ? "{\"ok\":true}" : "{\"ok\":false}");
        });
    server.addHandler(handlerWg);

    // Compat : retourne info AP (SSID + IP)
    server.on("/api/wifi", HTTP_GET, [](AsyncWebServerRequest *r) {
        JsonDocument doc;
        doc["ssid"] = WIFI_SSID;
        doc["password"] = WIFI_PASSWORD;
        String json; serializeJson(doc, json);
        r->send(200, "application/json", json);
    });

    // --- Captive portal --- iOS, Android, Windows détectent l'absence d'internet
    // et ouvrent automatiquement le navigateur sur notre page principale.
    auto redirect = [](AsyncWebServerRequest *r) {
        r->redirect("http://192.168.4.1/");
    };
    // iOS / macOS
    server.on("/hotspot-detect.html",           HTTP_GET, redirect);
    server.on("/library/test/success.html",     HTTP_GET, redirect);
    server.on("/canonical.html",                HTTP_GET, redirect);
    // Android
    server.on("/generate_204",                  HTTP_GET, redirect);
    server.on("/gen_204",                       HTTP_GET, redirect);
    server.on("/connecttest.txt",               HTTP_GET, redirect);
    // Windows
    server.on("/ncsi.txt",                      HTTP_GET, redirect);
    server.on("/redirect",                      HTTP_GET, redirect);
    server.on("/success.txt",                   HTTP_GET, redirect);

    // --- Fichiers statiques depuis LittleFS ---
    server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");

    server.onNotFound([](AsyncWebServerRequest *r){
        // Tout GET inconnu → portail captif (navigateur s'ouvre sur la page d'accueil)
        if (r->method() == HTTP_GET) {
            r->redirect("http://192.168.4.1/");
        } else {
            r->send(404, "text/plain", "Non trouvé");
        }
    });

    server.begin();
    Serial.println("Serveur web démarré sur http://192.168.4.1");
}
