#include <LittleFS.h>
#include <ArduinoJson.h>
#include <Arduino.h>
#include "epever_config.h"
#include "modbus_epever.h"
#include "debug_log.h"

#define FICHIER_CONFIG "/epever_config.json"

// ---------------------------------------------------------------------------
// Table des registres de configuration
// ---------------------------------------------------------------------------

const EpeverRegDef EPEVER_REGS[] = {
    // reg     key                      scale   min    max    wr    unit          label                         aide
    { 0x9000, "battery_type",          1.0f,    0,     4,    true, "",           "Type batterie",              "0=Perso, 1=Scellée (AGM), 2=GEL, 3=Liquide, 4=Lithium" },
    { 0x9001, "battery_capacity",      1.0f,    1,   9999,   true, "Ah",         "Capacité",                   "Capacité nominale en Ah. Influence les seuils de protection." },
    { 0x9002, "temp_compensation",     1.0f,   -9,     0,    true, "mV/°C/2V",   "Compensation température",   "Ajuste la tension de charge selon la température. Typique : -3 pour plomb-acide. Laisser 0 si inconnu." },
    { 0x9003, "high_volt_disconnect",  0.01f,  13.0f, 17.0f, true, "V",          "Déconnexion survoltage",     "Tension à laquelle le régulateur coupe pour protéger la batterie contre la surcharge." },
    { 0x9004, "charging_limit",        0.01f,  13.0f, 16.0f, true, "V",          "Limite de charge",           "Tension maximale autorisée pendant la charge." },
    { 0x9005, "overvolt_reconnect",    0.01f,  13.0f, 16.0f, true, "V",          "Reconnexion survoltage",     "En dessous de cette tension, la charge reprend après une coupure survoltage." },
    { 0x9006, "equalize_voltage",      0.01f,  13.0f, 16.0f, true, "V",          "Tension égalisation",        "Tension appliquée lors des cycles d'égalisation (désulfatation). Typique : 14.6V." },
    { 0x9007, "boost_voltage",         0.01f,  13.0f, 15.5f, true, "V",          "Tension boost",              "Tension d'absorption (charge rapide). Typique : 14.4V pour plomb-acide." },
    { 0x9008, "float_voltage",         0.01f,  13.0f, 14.5f, true, "V",          "Tension float",              "Tension de maintien après charge complète. Typique : 13.6V. Trop haute = usure prématurée." },
    { 0x9009, "boost_reconnect",       0.01f,  11.0f, 14.0f, true, "V",          "Reconnexion boost",          "Si la tension tombe sous ce seuil, la charge boost reprend automatiquement." },
    { 0x900A, "low_reconnect",         0.01f,  10.0f, 13.5f, true, "V",          "Reconnexion basse tension",  "Tension à laquelle la sortie de charge est réactivée après une coupure." },
    { 0x900B, "undervolt_warning",     0.01f,  10.0f, 13.0f, true, "V",          "Alerte basse tension",       "Déclenche une alarme sans couper. Prévenez avant que la batterie soit trop déchargée." },
    { 0x900C, "low_disconnect",        0.01f,   9.0f, 13.0f, true, "V",          "Déconnexion basse tension",  "La sortie charge est coupée sous cette tension pour protéger la batterie." },
    { 0x900D, "discharge_limit",       0.01f,   9.0f, 13.0f, true, "V",          "Limite de décharge",         "Tension minimale absolue. Ne jamais descendre en dessous." },
    { 0x906B, "rated_voltage",         1.0f,    0,     2,    true, "",           "Tension nominale système",   "0=Auto détecté, 1=Système 12V, 2=Système 24V. Laisser Auto si possible." },
    { 0x906C, "equalize_duration",     1.0f,    0,   180,    true, "min",        "Durée égalisation",          "Durée du cycle d'égalisation en minutes (0=désactivé)." },
    { 0x906D, "boost_duration",        1.0f,   10,   180,    true, "min",        "Durée boost",                "Durée maximale de la phase d'absorption en minutes." },
    { 0x906E, "bat_discharge_soc",     1.0f,   20,   100,    true, "%",          "SOC déconnexion",            "Seuil de charge minimum (%) avant coupure de la sortie." },
};
const uint8_t EPEVER_REGS_COUNT = sizeof(EPEVER_REGS) / sizeof(EPEVER_REGS[0]);

// ---------------------------------------------------------------------------
// Cache en RAM
// ---------------------------------------------------------------------------

static float         configCache[18];
static bool          configValide  = false;
static unsigned long tDerniereSync = 0;
static String        derniereErreur;

// ---------------------------------------------------------------------------
// Helpers internes
// ---------------------------------------------------------------------------

static int8_t indexPourReg(uint16_t reg) {
    for (uint8_t i = 0; i < EPEVER_REGS_COUNT; i++) {
        if (EPEVER_REGS[i].reg == reg) return (int8_t)i;
    }
    return -1;
}

static bool estBloc1(uint16_t reg) { return reg >= 0x9000u && reg <= 0x900Du; }
static bool estBloc2(uint16_t reg) { return reg >= 0x906Bu && reg <= 0x906Eu; }

// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------

void initConfigEpever() {
    debugLogf("[Config] initConfigEpever — %u registres", EPEVER_REGS_COUNT);
}

// ---------------------------------------------------------------------------
// Lecture depuis l'Epever
// ---------------------------------------------------------------------------

bool lireConfigEpever() {
    if (!modbusAcquire()) {
        derniereErreur = "Modbus occupé, réessayer dans quelques secondes";
        debugLogf("[Config] Lecture refusée: %s", derniereErreur.c_str());
        return false;
    }

    uint16_t raw1[EPEVER_BLOC1_QTY];
    uint16_t raw2[EPEVER_BLOC2_QTY];

    bool ok1 = modbusLireHolding(EPEVER_BLOC1_REG, EPEVER_BLOC1_QTY, raw1, 900);
    if (!ok1) {
        derniereErreur = "Erreur RS485 lecture bloc 0x9000 (tensions)";
        debugLogf("[Config] %s", derniereErreur.c_str());
        modbusRelease();
        return false;
    }

    bool ok2 = modbusLireHolding(EPEVER_BLOC2_REG, EPEVER_BLOC2_QTY, raw2, 700);
    if (!ok2) {
        // Bloc 2 non fatal : on garde les valeurs du cache ou zéro
        debugLogf("[Config] Bloc 0x906B indisponible — valeurs conservées");
    }

    modbusRelease();

    // Décoder bloc 1 (registres 0x9000 → 0x900D)
    for (uint8_t i = 0; i < EPEVER_BLOC1_QTY; i++) {
        int8_t idx = indexPourReg(0x9000u + i);
        if (idx < 0) continue;
        const EpeverRegDef &d = EPEVER_REGS[idx];
        if (d.scale == 1.0f && d.valMin < 0) {
            // valeur signée (ex: temp_compensation)
            configCache[idx] = (int16_t)raw1[i] * d.scale;
        } else {
            configCache[idx] = raw1[i] * d.scale;
        }
    }

    // Décoder bloc 2 (registres 0x906B → 0x906E)
    if (ok2) {
        for (uint8_t i = 0; i < EPEVER_BLOC2_QTY; i++) {
            int8_t idx = indexPourReg(0x906Bu + i);
            if (idx < 0) continue;
            configCache[idx] = raw2[i] * EPEVER_REGS[idx].scale;
        }
    }

    configValide   = true;
    tDerniereSync  = millis();
    derniereErreur = "";
    debugLogf("[Config] Lecture OK — Type:%d Capa:%dAh Float:%.2fV Boost:%.2fV",
              (int)configCache[indexPourReg(0x9000)],
              (int)configCache[indexPourReg(0x9001)],
              configCache[indexPourReg(0x9008)],
              configCache[indexPourReg(0x9007)]);
    return true;
}

// ---------------------------------------------------------------------------
// Écriture vers l'Epever
// ---------------------------------------------------------------------------

bool ecrireConfigEpever(JsonObject obj, String &erreur) {
    if (!configValide) {
        erreur = "Lecture préalable requise — cliquer Lire d'abord";
        return false;
    }
    if (!modbusAcquire()) {
        erreur = "Modbus occupé, réessayer dans quelques secondes";
        return false;
    }

    // Validation complète avant d'écrire quoi que ce soit
    for (uint8_t i = 0; i < EPEVER_REGS_COUNT; i++) {
        const EpeverRegDef &d = EPEVER_REGS[i];
        if (!obj[d.key].is<float>() && !obj[d.key].is<int>()) continue;
        float val = obj[d.key].as<float>();
        if (val < d.valMin || val > d.valMax) {
            erreur = String(d.label) + " hors plage [" + String(d.valMin, 2) + "–" + String(d.valMax, 2) + "]";
            modbusRelease();
            return false;
        }
    }

    // Vérification cohérence tensions clés
    auto getVal = [&](const char *key) -> float {
        if (obj[key].is<float>() || obj[key].is<int>()) return obj[key].as<float>();
        int8_t idx = indexPourReg(0); // recalculé
        for (uint8_t i = 0; i < EPEVER_REGS_COUNT; i++) {
            if (strcmp(EPEVER_REGS[i].key, key) == 0) return configCache[i];
        }
        return 0.0f;
    };

    float floatV  = getVal("float_voltage");
    float boostV  = getVal("boost_voltage");
    float eqV     = getVal("equalize_voltage");
    float discoV  = getVal("low_disconnect");
    float reconV  = getVal("low_reconnect");

    if (floatV >= boostV) {
        erreur = "Float Voltage doit être < Boost Voltage";
        modbusRelease();
        return false;
    }
    if (discoV >= reconV) {
        erreur = "Déconnexion basse tension doit être < Reconnexion";
        modbusRelease();
        return false;
    }
    if (boostV > eqV && eqV > 0) {
        erreur = "Boost Voltage doit être ≤ Equalize Voltage";
        modbusRelease();
        return false;
    }

    // Écriture registre par registre
    bool ok = true;
    for (uint8_t i = 0; i < EPEVER_REGS_COUNT; i++) {
        const EpeverRegDef &d = EPEVER_REGS[i];
        if (!d.writable) continue;
        if (!obj[d.key].is<float>() && !obj[d.key].is<int>()) continue;

        float    val = obj[d.key].as<float>();
        uint16_t raw;

        if (d.scale == 1.0f && d.valMin < 0) {
            raw = (uint16_t)(int16_t)round(val);   // signé (temp_compensation)
        } else if (d.scale < 1.0f) {
            raw = (uint16_t)round(val / d.scale);  // tension ÷ 0.01
        } else {
            raw = (uint16_t)round(val);             // entier brut
        }

        if (!modbusEcrireHolding(d.reg, 1, &raw, 800)) {
            erreur = String("Erreur RS485 écriture ") + d.label;
            ok = false;
            break;
        }

        configCache[i] = val;
        delay(15);
    }

    modbusRelease();

    if (ok) {
        derniereErreur = "";
        debugLogf("[Config] Écriture OK");
    } else {
        derniereErreur = erreur;
        debugLogf("[Config] Écriture ERREUR: %s", erreur.c_str());
    }
    return ok;
}

// ---------------------------------------------------------------------------
// Persistance LittleFS
// ---------------------------------------------------------------------------

bool sauvegarderConfigJson() {
    if (!configValide) {
        debugLogf("[Config] Sauvegarde refusée: cache non valide");
        return false;
    }

    JsonDocument doc;
    JsonObject values = doc["values"].to<JsonObject>();
    for (uint8_t i = 0; i < EPEVER_REGS_COUNT; i++) {
        const EpeverRegDef &d = EPEVER_REGS[i];
        if (d.scale < 1.0f) {
            values[d.key] = roundf(configCache[i] * 100.0f) / 100.0f;
        } else {
            values[d.key] = (int)configCache[i];
        }
    }

    File f = LittleFS.open(FICHIER_CONFIG, "w");
    if (!f) {
        debugLogf("[Config] Impossible d'ouvrir %s en écriture", FICHIER_CONFIG);
        return false;
    }
    serializeJson(doc, f);
    f.close();
    debugLogf("[Config] Sauvegardé dans %s", FICHIER_CONFIG);
    return true;
}

bool restaurerConfigJson(String &erreur) {
    if (!LittleFS.exists(FICHIER_CONFIG)) {
        erreur = "Aucune sauvegarde trouvée";
        return false;
    }

    File f = LittleFS.open(FICHIER_CONFIG, "r");
    if (!f) { erreur = "Impossible de lire la sauvegarde"; return false; }

    JsonDocument doc;
    DeserializationError e = deserializeJson(doc, f);
    f.close();

    if (e) { erreur = "JSON invalide dans la sauvegarde"; return false; }

    JsonObject values = doc["values"].as<JsonObject>();
    if (values.isNull()) { erreur = "Format sauvegarde invalide"; return false; }

    return ecrireConfigEpever(values, erreur);
}

// ---------------------------------------------------------------------------
// Sérialisation JSON pour les API
// ---------------------------------------------------------------------------

void getConfigJson(String &out) {
    JsonDocument doc;
    doc["ok"]     = configValide;
    doc["valide"] = configValide;
    doc["uptime"] = millis();

    if (!derniereErreur.isEmpty()) doc["erreur"] = derniereErreur;

    JsonObject values = doc["values"].to<JsonObject>();
    if (configValide) {
        for (uint8_t i = 0; i < EPEVER_REGS_COUNT; i++) {
            const EpeverRegDef &d = EPEVER_REGS[i];
            if (d.scale < 1.0f) {
                values[d.key] = roundf(configCache[i] * 100.0f) / 100.0f;
            } else {
                values[d.key] = (int)configCache[i];
            }
        }
    }

    // Schéma (labels + unités + aide + plages) pour génération UI
    JsonArray schema = doc["schema"].to<JsonArray>();
    for (uint8_t i = 0; i < EPEVER_REGS_COUNT; i++) {
        const EpeverRegDef &d = EPEVER_REGS[i];
        JsonObject r = schema.add<JsonObject>();
        r["key"]      = d.key;
        r["label"]    = d.label;
        r["unit"]     = d.unit;
        r["aide"]     = d.aide;
        r["min"]      = d.valMin;
        r["max"]      = d.valMax;
        r["writable"] = d.writable;
        r["scale"]    = d.scale;
    }

    serializeJson(doc, out);
}

void getConfigSauvegardeJson(String &out) {
    if (!LittleFS.exists(FICHIER_CONFIG)) {
        out = "{\"ok\":false,\"erreur\":\"Aucune sauvegarde\"}";
        return;
    }
    File f = LittleFS.open(FICHIER_CONFIG, "r");
    if (!f) { out = "{\"ok\":false,\"erreur\":\"Lecture impossible\"}"; return; }
    out = f.readString();
    f.close();
}
