#include "historique.h"
#include "state.h"
#include "debug_log.h"
#include <LittleFS.h>

// ─── Dimensions ───────────────────────────────────────────────────────────────
#define HIRES_MAX    240       // 4h × 60 min, une mesure/min
#define LORES_MAX    312       // 26h × 12 pts/h (5 min)
#define FICHIER_HIST "/hist.bin"

// ─── Format binaire du fichier (13 octets/entrée) ────────────────────────────
struct __attribute__((packed)) HistEntry {
    float   bat;
    float   pv;
    float   load;
    uint8_t soc;
};

// ─── Buffers haute résolution (RAM uniquement) ───────────────────────────────
static float   hrBat[HIRES_MAX], hrPV[HIRES_MAX], hrLoad[HIRES_MAX];
static uint8_t hrSOC[HIRES_MAX];
static uint16_t hrTete = 0, hrN = 0;

// ─── Buffers basse résolution (RAM + fichier) ────────────────────────────────
static float   lrBat[LORES_MAX], lrPV[LORES_MAX], lrLoad[LORES_MAX];
static uint8_t lrSOC[LORES_MAX];
static uint16_t lrTete = 0, lrN = 0;

// ─── Accumulateurs pour la moyenne 5 min ─────────────────────────────────────
static float    accBat = 0, accPV = 0, accLoad = 0;
static uint16_t accSOC = 0, accN  = 0;
static uint8_t  lrDepuisHr = 0;   // pts lores ajoutés depuis la dernière sauvegarde

// ─── Timers ───────────────────────────────────────────────────────────────────
static unsigned long tDernMin  = 0;
static unsigned long tDern5Min = 0;
static unsigned long tDernHr   = 0;
static bool premierPointAjoute = false;

// ─── Index ordonné dans un ring buffer ───────────────────────────────────────
static inline uint16_t hrIdx(uint16_t i) {
    return (uint16_t)((hrTete - hrN + i + HIRES_MAX) % HIRES_MAX);
}
static inline uint16_t lrIdx(uint16_t i) {
    return (uint16_t)((lrTete - lrN + i + LORES_MAX) % LORES_MAX);
}

// ─── Ajout d'un point hires ──────────────────────────────────────────────────
static void pushHires() {
    hrBat[hrTete]  = state.battery;
    hrPV[hrTete]   = state.pv;
    hrLoad[hrTete] = state.loadPower;
    hrSOC[hrTete]  = state.batSOC;
    hrTete = (hrTete + 1) % HIRES_MAX;
    if (hrN < HIRES_MAX) hrN++;

    accBat  += state.battery;
    accPV   += state.pv;
    accLoad += state.loadPower;
    accSOC  += state.batSOC;
    accN++;
    debugLogf("[HIST] Point hires #%u — bat=%.2fV pv=%.2fV load=%.1fW soc=%u",
              hrN, state.battery, state.pv, state.loadPower, state.batSOC);
}

// ─── Flush de la moyenne 5 min vers lores ────────────────────────────────────
static void pushLores() {
    if (accN == 0) return;
    lrBat[lrTete]  = accBat  / accN;
    lrPV[lrTete]   = accPV   / accN;
    lrLoad[lrTete] = accLoad / accN;
    lrSOC[lrTete]  = (uint8_t)(accSOC / accN);
    lrTete = (lrTete + 1) % LORES_MAX;
    if (lrN < LORES_MAX) lrN++;
    lrDepuisHr++;
    debugLogf("[HIST] Point lores #%u — moyennes sur %u pt(s): bat=%.2fV pv=%.2fV load=%.1fW soc=%u",
              lrN, accN, lrBat[(lrTete + LORES_MAX - 1) % LORES_MAX],
              lrPV[(lrTete + LORES_MAX - 1) % LORES_MAX],
              lrLoad[(lrTete + LORES_MAX - 1) % LORES_MAX],
              lrSOC[(lrTete + LORES_MAX - 1) % LORES_MAX]);
    accBat = accPV = accLoad = 0;
    accSOC = 0; accN = 0;
}

// ─── Sauvegarde horaire vers /hist.bin ───────────────────────────────────────
static void sauvegarderHeure() {
    if (lrDepuisHr == 0) return;

    // Lire les entrées existantes (max LORES_MAX)
    HistEntry buf[LORES_MAX];
    uint16_t existant = 0;
    File fr = LittleFS.open(FICHIER_HIST, "r");
    if (fr) {
        existant = (uint16_t)min((size_t)(fr.size() / sizeof(HistEntry)),
                                 (size_t)LORES_MAX);
        fr.read((uint8_t*)buf, existant * sizeof(HistEntry));
        fr.close();
    }

    // Ajouter les lrDepuisHr nouvelles entrées depuis le ring lores
    uint16_t debut = (lrN >= lrDepuisHr) ? (lrN - lrDepuisHr) : 0;
    for (uint16_t i = debut; i < lrN; i++) {
        if (existant >= LORES_MAX) {
            memmove(buf, buf + 1, (existant - 1) * sizeof(HistEntry));
            existant--;
        }
        uint16_t idx = lrIdx(i);
        buf[existant++] = { lrBat[idx], lrPV[idx], lrLoad[idx], lrSOC[idx] };
    }

    // Réécrire le fichier
    File fw = LittleFS.open(FICHIER_HIST, "w");
    if (fw) {
        fw.write((uint8_t*)buf, existant * sizeof(HistEntry));
        fw.close();
        Serial.printf("[HIST] Sauvegarde %d pts → %d total (%dB)\n",
                      lrDepuisHr, existant,
                      (int)(existant * sizeof(HistEntry)));
    } else {
        Serial.println("[HIST] Erreur écriture hist.bin");
    }
    lrDepuisHr = 0;
}

// ─── Chargement du fichier au démarrage ──────────────────────────────────────
static void chargerFichier() {
    File f = LittleFS.open(FICHIER_HIST, "r");
    if (!f) { Serial.println("[HIST] Aucun fichier — démarrage à zéro"); return; }

    uint16_t n = (uint16_t)(f.size() / sizeof(HistEntry));
    if (n > LORES_MAX) n = LORES_MAX;
    Serial.printf("[HIST] Chargement de %d pts depuis hist.bin\n", n);

    HistEntry e;
    for (uint16_t i = 0; i < n; i++) {
        f.read((uint8_t*)&e, sizeof(e));
        lrBat[lrTete]  = e.bat;
        lrPV[lrTete]   = e.pv;
        lrLoad[lrTete] = e.load;
        lrSOC[lrTete]  = e.soc;
        lrTete = (lrTete + 1) % LORES_MAX;
        if (lrN < LORES_MAX) lrN++;
    }
    f.close();
}

// ─── Sérialisation JSON générique ────────────────────────────────────────────
static void serJson(String &out, const char *mode, uint16_t n,
                    uint16_t step_s,
                    float *bat, float *pv, float *load, uint8_t *soc,
                    uint16_t maxBuf,
                    uint16_t (*idxFn)(uint16_t)) {
    char tmp[12];
    out.reserve(n * 26 + 80);
    out  = "{\"mode\":\""; out += mode;
    out += "\",\"step\":"; out += step_s;
    out += ",\"n\":";      out += n;

    out += ",\"b\":[";
    for (uint16_t i = 0; i < n; i++) {
        if (i) out += ',';
        snprintf(tmp, sizeof(tmp), "%.2f", bat[idxFn(i)]);
        out += tmp;
    }
    out += "],\"p\":[";
    for (uint16_t i = 0; i < n; i++) {
        if (i) out += ',';
        snprintf(tmp, sizeof(tmp), "%.2f", pv[idxFn(i)]);
        out += tmp;
    }
    out += "],\"l\":[";
    for (uint16_t i = 0; i < n; i++) {
        if (i) out += ',';
        snprintf(tmp, sizeof(tmp), "%.1f", load[idxFn(i)]);
        out += tmp;
    }
    out += "],\"s\":[";
    for (uint16_t i = 0; i < n; i++) {
        if (i) out += ',';
        out += soc[idxFn(i)];
    }
    out += "]}";
    (void)maxBuf;
}

// ─── API publique ─────────────────────────────────────────────────────────────
void initHistorique() {
    hrTete = hrN = lrTete = lrN = 0;
    accBat = accPV = accLoad = 0;
    accSOC = accN = lrDepuisHr = 0;
    premierPointAjoute = false;
    unsigned long t = millis();
    tDernMin = tDern5Min = tDernHr = t;
    chargerFichier();
    debugLogf("[HIST] Init — hires=%u lores=%u", hrN, lrN);
}

void gererHistorique() {
    if (!state.rs485_ok) return;
    unsigned long maintenant = millis();

    if (!premierPointAjoute) {
        premierPointAjoute = true;
        tDernMin = maintenant;
        pushHires();
    } else if (maintenant - tDernMin >= 60000UL) {
        tDernMin = maintenant;
        pushHires();
    }
    if (maintenant - tDern5Min >= 300000UL) {
        tDern5Min = maintenant;
        pushLores();
    }
    if (maintenant - tDernHr >= 3600000UL) {
        tDernHr = maintenant;
        sauvegarderHeure();
    }
}

// Lores : jusqu'à 30h, résolution 5 min
void getHistoriqueJson(String &out) {
    serJson(out, "lores", lrN, 300,
            lrBat, lrPV, lrLoad, lrSOC, LORES_MAX, lrIdx);
}

// Hires : jusqu'à 4h, résolution 1 min
void getHistoriqueHiresJson(String &out) {
    serJson(out, "hires", hrN, 60,
            hrBat, hrPV, hrLoad, hrSOC, HIRES_MAX, hrIdx);
}

// Export CSV lores (30h, pas 5 min) — temps relatif en minutes depuis maintenant
void getHistoriqueCsv(String &out) {
    out.reserve(lrN * 32 + 64);
    out = "temps_min,batterie_V,pv_V,charge_W,soc_pct\r\n";
    char tmp[48];
    for (uint16_t i = 0; i < lrN; i++) {
        uint16_t idx = lrIdx(i);
        int32_t min  = -((int32_t)(lrN - 1 - i) * 5);
        snprintf(tmp, sizeof(tmp), "%ld,%.2f,%.2f,%.1f,%d\r\n",
                 (long)min, lrBat[idx], lrPV[idx], lrLoad[idx], lrSOC[idx]);
        out += tmp;
    }
}

void getHistoriqueStatusJson(String &out) {
    out = "{";
    out += "\"hires_n\":"; out += hrN;
    out += ",\"hires_max\":"; out += HIRES_MAX;
    out += ",\"lores_n\":"; out += lrN;
    out += ",\"lores_max\":"; out += LORES_MAX;
    out += ",\"acc_n\":"; out += accN;
    out += ",\"rs485_ok\":"; out += state.rs485_ok ? "true" : "false";
    out += ",\"last_update\":"; out += state.last_update;
    out += ",\"millis\":"; out += millis();
    out += ",\"next_hires_ms\":";
    unsigned long now = millis();
    out += (now - tDernMin >= 60000UL) ? 0 : (60000UL - (now - tDernMin));
    out += ",\"next_lores_ms\":";
    out += (now - tDern5Min >= 300000UL) ? 0 : (300000UL - (now - tDern5Min));
    out += "}";
}
