#include <LittleFS.h>
#include <ArduinoJson.h>
#include <Arduino.h>
#include "config.h"
#include "state.h"
#include "rules.h"

#define MAX_REGLES     20
#define FICHIER_REGLES "/rules.json"

struct Regle {
    int      id;
    bool     enabled;
    // Déclencheurs
    int8_t   sun;         // -1=ignoré, 0=nuit requis, 1=jour requis
    int8_t   di1;         // -1=ignoré, 0=ouvert requis, 1=fermé requis
    int8_t   di2;         // -1=ignoré, 0=ouvert requis, 1=fermé requis
    // Conditions
    float    batteryMin;  // seuil min batterie en V (0 = ignoré)
    float    batteryMax;  // seuil max batterie en V (0 = ignoré)
    float    pvMin;       // seuil min PV en V (0 = ignoré)
    float    pvMax;       // seuil max PV en V (0 = ignoré)
    // Action
    uint8_t  relay;       // 1 ou 2
    bool     etat;        // true=ON, false=OFF
    uint32_t delai;       // délai avant action (secondes)
    float    hysteresis;  // bande morte en V

    // État runtime — non persisté
    bool          delaiEnCours;
    unsigned long tDebutDelai;
    bool          estActif;
};

static Regle         regles[MAX_REGLES];
static int           nbRegles       = 0;
static unsigned long tDerniereEval  = 0;

// --- Persistance ---

static int8_t parseTri(JsonObject &obj, const char *key) {
    if (!obj[key].is<bool>()) return -1;
    return obj[key].as<bool>() ? 1 : 0;
}

static void jsonVersRegle(JsonObject obj, Regle &r) {
    r.enabled    = obj["enabled"]    | true;
    r.sun        = parseTri(obj, "sun");
    r.di1        = parseTri(obj, "di1");
    r.di2        = parseTri(obj, "di2");
    r.batteryMin = obj["battery_min"] | 0.0f;
    r.batteryMax = obj["battery_max"] | 0.0f;
    r.pvMin      = obj["pv_min"]      | 0.0f;
    r.pvMax      = obj["pv_max"]      | 0.0f;
    r.relay      = obj["relay"]       | 1;
    r.etat       = obj["state"]       | false;
    r.delai      = obj["delay"]       | 0u;
    r.hysteresis = obj["hysteresis"]  | 0.0f;
    r.delaiEnCours = false;
    r.tDebutDelai  = 0;
    r.estActif     = false;
}

static void chargerRegles() {
    nbRegles = 0;
    if (!LittleFS.exists(FICHIER_REGLES)) {
        Serial.println("rules.json absent — aucune règle chargée");
        return;
    }
    File f = LittleFS.open(FICHIER_REGLES, "r");
    if (!f) { Serial.println("Erreur ouverture rules.json"); return; }

    JsonDocument doc;
    if (deserializeJson(doc, f)) {
        Serial.println("Erreur parsing rules.json");
        f.close();
        return;
    }
    f.close();

    for (JsonObject obj : doc.as<JsonArray>()) {
        if (nbRegles >= MAX_REGLES) break;
        regles[nbRegles].id = obj["id"] | (nbRegles + 1);
        jsonVersRegle(obj, regles[nbRegles]);
        nbRegles++;
    }
    Serial.printf("%d règle(s) chargée(s)\n", nbRegles);
}

static bool sauvegarderRegles() {
    File f = LittleFS.open(FICHIER_REGLES, "w");
    if (!f) { Serial.println("Erreur écriture rules.json"); return false; }

    JsonDocument doc;
    JsonArray arr = doc.to<JsonArray>();
    reglesToJson(arr);
    serializeJson(doc, f);
    f.close();
    return true;
}

// --- Logique d'évaluation ---

// Hystérésis : quand la règle est active, les seuils sont relâchés d'une
// bande `hysteresis` pour éviter les oscillations autour du point de consigne.
static bool conditionsSatisfaites(const Regle &r) {
    // Seuils batterie avec hystérésis si la règle était déjà active
    float batMinEff = (r.hysteresis > 0 && r.estActif) ? r.batteryMin - r.hysteresis : r.batteryMin;
    float batMaxEff = (r.hysteresis > 0 && r.estActif) ? r.batteryMax + r.hysteresis : r.batteryMax;
    float pvMinEff  = (r.hysteresis > 0 && r.estActif) ? r.pvMin  - r.hysteresis : r.pvMin;
    float pvMaxEff  = (r.hysteresis > 0 && r.estActif) ? r.pvMax  + r.hysteresis : r.pvMax;
    // Déclencheurs
    if (r.sun >= 0 && (bool)(r.sun == 1) != state.sun)        return false;
    if (r.di1 >= 0 && (bool)(r.di1 == 1) != state.di1)        return false;
    if (r.di2 >= 0 && (bool)(r.di2 == 1) != state.di2)        return false;
    // Conditions
    if (r.batteryMin > 0 && state.battery < batMinEff)        return false;
    if (r.batteryMax > 0 && state.battery > batMaxEff)        return false;
    if (r.pvMin      > 0 && state.pv      < pvMinEff)         return false;
    if (r.pvMax      > 0 && state.pv      > pvMaxEff)         return false;
    return true;
}

static void appliquerAction(const Regle &r) {
    if (r.relay == 1) {
        state.relay1 = r.etat;
        digitalWrite(PIN_RELAY1, r.etat ? HIGH : LOW);
    } else if (r.relay == 2) {
        state.relay2 = r.etat;
        digitalWrite(PIN_RELAY2, r.etat ? HIGH : LOW);
    }
    Serial.printf("Règle %d appliquée — relais %d : %s\n", r.id, r.relay, r.etat ? "ON" : "OFF");
}

// --- API publique ---

void initRegles() {
    chargerRegles();
}

void gererRegles() {

    unsigned long maintenant = millis();
    if (maintenant - tDerniereEval < INTERVALLE_REGLES) return;
    tDerniereEval = maintenant;

    for (int i = 0; i < nbRegles; i++) {
        Regle &r = regles[i];
        if (!r.enabled) continue;

        if (conditionsSatisfaites(r)) {
            r.estActif = true;
            if (r.delai == 0) {
                appliquerAction(r);
            } else if (!r.delaiEnCours) {
                r.delaiEnCours = true;
                r.tDebutDelai  = maintenant;
                Serial.printf("Règle %d — délai %ds démarré\n", r.id, r.delai);
            } else if (maintenant - r.tDebutDelai >= (unsigned long)r.delai * 1000UL) {
                appliquerAction(r);
                r.delaiEnCours = false;
            }
        } else {
            r.estActif = false;
            if (r.delaiEnCours) {
                r.delaiEnCours = false;
                Serial.printf("Règle %d — conditions perdues, délai annulé\n", r.id);
            }
        }
    }
}

void reglesToJson(JsonArray arr) {
    for (int i = 0; i < nbRegles; i++) {
        const Regle &r = regles[i];
        JsonObject obj  = arr.add<JsonObject>();
        obj["id"]       = r.id;
        obj["enabled"]  = r.enabled;
        if (r.sun >= 0)       obj["sun"]         = (bool)(r.sun == 1);
        if (r.di1 >= 0)       obj["di1"]         = (bool)(r.di1 == 1);
        if (r.di2 >= 0)       obj["di2"]         = (bool)(r.di2 == 1);
        if (r.batteryMin > 0) obj["battery_min"] = r.batteryMin;
        if (r.batteryMax > 0) obj["battery_max"] = r.batteryMax;
        if (r.pvMin > 0)      obj["pv_min"]      = r.pvMin;
        if (r.pvMax > 0)      obj["pv_max"]      = r.pvMax;
        obj["relay"]    = r.relay;
        obj["state"]    = r.etat;
        obj["delay"]    = r.delai;
        if (r.hysteresis > 0) obj["hysteresis"]  = r.hysteresis;
    }
}

bool ajouterRegle(JsonObject obj) {
    if (nbRegles >= MAX_REGLES) return false;
    int maxId = 0;
    for (int i = 0; i < nbRegles; i++) if (regles[i].id > maxId) maxId = regles[i].id;
    regles[nbRegles].id = maxId + 1;
    jsonVersRegle(obj, regles[nbRegles]);
    nbRegles++;
    return sauvegarderRegles();
}

bool supprimerRegle(int id) {
    for (int i = 0; i < nbRegles; i++) {
        if (regles[i].id != id) continue;
        for (int j = i; j < nbRegles - 1; j++) regles[j] = regles[j + 1];
        nbRegles--;
        return sauvegarderRegles();
    }
    return false;
}

bool toggleRegle(int id) {
    for (int i = 0; i < nbRegles; i++) {
        if (regles[i].id != id) continue;
        regles[i].enabled = !regles[i].enabled;
        return sauvegarderRegles();
    }
    return false;
}
