#include "HistoryStore.hpp"
#include "SdCard.hpp"
#include "esp_log.h"
#include "esp_timer.h"
#include <cmath>
#include <cstdio>
#include <cstring>
#include <sys/time.h>

static const char* TAG = "HistoryStore";
static constexpr uint32_t HISTORY_MAGIC = 0x4B584831; // KXH1
static constexpr uint16_t HISTORY_VERSION = 1;
static constexpr const char* HISTORY_FILE = "/sdcard/knx_history.bin";
static constexpr int64_t HISTORY_SAVE_INTERVAL_US = 300000000; // 5 minutes

#pragma pack(push, 1)
struct HistoryFileHeader {
    uint32_t magic;
    uint16_t version;
    uint16_t seriesCount;
    uint16_t fineCapacity;
    uint16_t coarseCapacity;
    uint32_t fineInterval;
    uint32_t coarseInterval;
};

struct HistoryFileSeriesHeader {
    uint16_t groupAddr;
    uint8_t source;
    uint8_t reserved;
    uint16_t fineCount;
    uint16_t fineHead;
    uint16_t coarseCount;
    uint16_t coarseHead;
    uint8_t hasLatest;
    uint8_t reserved2[3];
    int32_t latestTs;
    float latestValue;
};
#pragma pack(pop)

HistoryStore& HistoryStore::instance() {
    static HistoryStore inst;
    return inst;
}

HistoryStore::HistoryStore() {
    mutex_ = xSemaphoreCreateMutex();
}

HistoryStore::~HistoryStore() {
    if (mutex_) {
        vSemaphoreDelete(mutex_);
    }
}

bool HistoryStore::isNumericSource(TextSource source) {
    return source == TextSource::KNX_DPT_TEMP ||
           source == TextSource::KNX_DPT_PERCENT ||
           source == TextSource::KNX_DPT_POWER ||
           source == TextSource::KNX_DPT_ENERGY ||
           source == TextSource::KNX_DPT_DECIMALFACTOR;
}

bool HistoryStore::keysEqual(const SeriesKey& a, const SeriesKey& b) {
    return a.addr == b.addr && a.source == b.source;
}

HistoryStore::HistorySeries* HistoryStore::findSeries(uint16_t groupAddr, TextSource source) {
    SeriesKey key{groupAddr, source};
    for (size_t i = 0; i < series_.size(); ++i) {
        if (series_[i].active && keysEqual(series_[i].key, key)) {
            return &series_[i];
        }
    }
    return nullptr;
}

const HistoryStore::HistorySeries* HistoryStore::findSeries(uint16_t groupAddr, TextSource source) const {
    SeriesKey key{groupAddr, source};
    for (size_t i = 0; i < series_.size(); ++i) {
        if (series_[i].active && keysEqual(series_[i].key, key)) {
            return &series_[i];
        }
    }
    return nullptr;
}

HistoryStore::HistorySeries* HistoryStore::findSeriesByKey(const SeriesKey& key) {
    for (size_t i = 0; i < series_.size(); ++i) {
        if (keysEqual(series_[i].key, key)) {
            return &series_[i];
        }
    }
    return nullptr;
}

const HistoryStore::HistorySeries* HistoryStore::findSeriesByKey(const SeriesKey& key) const {
    for (size_t i = 0; i < series_.size(); ++i) {
        if (keysEqual(series_[i].key, key)) {
            return &series_[i];
        }
    }
    return nullptr;
}

void HistoryStore::configureFromConfig(const GuiConfig& config) {
    xSemaphoreTake(mutex_, portMAX_DELAY);
    std::array<SeriesKey, HISTORY_MAX_SERIES> needed = {};
    size_t neededCount = 0;

    auto addSeries = [&](uint16_t addr, TextSource source) {
        if (addr == 0 || !isNumericSource(source)) return;
        SeriesKey key{addr, source};
        for (size_t i = 0; i < neededCount; ++i) {
            if (keysEqual(needed[i], key)) return;
        }
        if (neededCount >= HISTORY_MAX_SERIES) return;
        needed[neededCount++] = key;
    };

    for (size_t s = 0; s < config.screenCount; ++s) {
        const ScreenConfig& screen = config.screens[s];
        for (size_t i = 0; i < screen.widgetCount; ++i) {
            const WidgetConfig& w = screen.widgets[i];
            if (w.type != WidgetType::CHART) continue;
            uint8_t count = w.chartSeriesCount;
            if (count > CHART_MAX_SERIES) count = CHART_MAX_SERIES;
            for (uint8_t si = 0; si < count; ++si) {
                addSeries(w.chartKnxAddress[si], w.chartTextSource[si]);
            }
        }
    }

    std::array<bool, HISTORY_MAX_SERIES> keep = {};
    for (size_t i = 0; i < neededCount; ++i) {
        const SeriesKey& key = needed[i];
        HistorySeries* existing = findSeriesByKey(key);
        if (existing) {
            size_t idx = static_cast<size_t>(existing - series_.data());
            keep[idx] = true;
            continue;
        }

        HistorySeries* slot = nullptr;
        for (size_t si = 0; si < series_.size(); ++si) {
            if (!keep[si] && !series_[si].active) {
                slot = &series_[si];
                keep[si] = true;
                break;
            }
        }

        if (slot) {
            slot->key = key;
            slot->active = true;
            slot->hasLatest = false;
            slot->fine.clear();
            slot->coarse.clear();
            slot->lastFineSampleTs = 0;
            slot->lastCoarseSampleTs = 0;
        }
    }

    for (size_t i = 0; i < series_.size(); ++i) {
        series_[i].active = keep[i];
    }
    xSemaphoreGive(mutex_);
}

bool HistoryStore::isTracked(uint16_t groupAddr, TextSource source) const {
    xSemaphoreTake(mutex_, portMAX_DELAY);
    bool tracked = findSeries(groupAddr, source) != nullptr;
    xSemaphoreGive(mutex_);
    return tracked;
}

bool HistoryStore::updateLatest(uint16_t groupAddr, TextSource source, float value) {
    xSemaphoreTake(mutex_, portMAX_DELAY);
    HistorySeries* series = findSeries(groupAddr, source);
    if (!series) {
        xSemaphoreGive(mutex_);
        return false;
    }

    int64_t nowSec = now();
    series->latestValue = value;
    series->latestTs = static_cast<int32_t>(nowSec);
    series->hasLatest = true;
    xSemaphoreGive(mutex_);
    return true;
}

int64_t HistoryStore::now() const {
    return (int64_t)time(nullptr);
}

int32_t HistoryStore::periodSeconds(ChartPeriod period) const {
    switch (period) {
        case ChartPeriod::HOUR_1: return 3600;
        case ChartPeriod::HOUR_3: return 3 * 3600;
        case ChartPeriod::HOUR_5: return 5 * 3600;
        case ChartPeriod::HOUR_12: return 12 * 3600;
        case ChartPeriod::HOUR_24: return 24 * 3600;
        case ChartPeriod::MONTH_1: return HISTORY_MONTH_SECONDS;
        default: return 3600;
    }
}

bool HistoryStore::fillChartSeries(uint16_t groupAddr, TextSource source, ChartPeriod period,
                                   int32_t* outValues, size_t outCount) const {
    if (!outValues || outCount == 0) return false;
    if (outCount > CHART_POINT_COUNT) outCount = CHART_POINT_COUNT;
    for (size_t i = 0; i < outCount; ++i) {
        outValues[i] = NO_POINT;
    }

    xSemaphoreTake(mutex_, portMAX_DELAY);
    const HistorySeries* series = findSeries(groupAddr, source);
    if (!series) {
        xSemaphoreGive(mutex_);
        return false;
    }

    int64_t nowSec = now();
    int32_t window = periodSeconds(period);
    if (window <= 0) {
        xSemaphoreGive(mutex_);
        return false;
    }

    int64_t start = nowSec - window;

    std::array<float, CHART_POINT_COUNT> sums = {};
    std::array<uint16_t, CHART_POINT_COUNT> counts = {};

    auto accumulate = [&](const HistoryPoint& p) {
        if (p.ts < start || p.ts > nowSec) return;
        size_t bucket = static_cast<size_t>(((p.ts - start) * outCount) / window);
        if (bucket >= outCount) bucket = outCount - 1;
        sums[bucket] += p.value;
        counts[bucket]++;
    };

    if (period == ChartPeriod::MONTH_1) {
        series->coarse.forEach(accumulate);
    } else {
        series->fine.forEach(accumulate);
    }

    if (series->hasLatest) {
        HistoryPoint latest{series->latestTs, series->latestValue};
        accumulate(latest);
    }
    xSemaphoreGive(mutex_);

    bool hasData = false;
    int32_t lastValidValue = NO_POINT;

    for (size_t i = 0; i < outCount; ++i) {
        if (counts[i] > 0) {
            float avg = sums[i] / counts[i];
            int32_t val = static_cast<int32_t>(lrintf(avg));
            outValues[i] = val;
            lastValidValue = val;
            hasData = true;
        } else {
            outValues[i] = lastValidValue;
            if (lastValidValue != NO_POINT) {
                hasData = true;
            }
        }
    }

    return hasData;
}

bool HistoryStore::tick() {
    xSemaphoreTake(mutex_, portMAX_DELAY);
    int64_t nowSec = now();
    
    // Only collect data if time is roughly synced (after 2020)
    if (nowSec < 1577836800LL) {
        xSemaphoreGive(mutex_);
        return false;
    }

    bool added = false;
    for (size_t i = 0; i < series_.size(); ++i) {
        HistorySeries& series = series_[i];
        if (!series.active || !series.hasLatest) continue;

        if (series.fine.count == 0 || nowSec - series.lastFineSampleTs >= HISTORY_FINE_INTERVAL) {
            series.fine.push({static_cast<int32_t>(nowSec), series.latestValue});
            series.lastFineSampleTs = static_cast<int32_t>(nowSec);
            added = true;
        }

        if (series.coarse.count == 0 || nowSec - series.lastCoarseSampleTs >= HISTORY_COARSE_INTERVAL) {
            series.coarse.push({static_cast<int32_t>(nowSec), series.latestValue});
            series.lastCoarseSampleTs = static_cast<int32_t>(nowSec);
            added = true;
        }
    }

    if (added) {
        dirty_ = true;
    }
    xSemaphoreGive(mutex_);
    return added;
}

void HistoryStore::performAutoSave() {
    xSemaphoreTake(mutex_, portMAX_DELAY);
    bool shouldSave = false;
    int64_t nowSec = now();
    int64_t monoUs = esp_timer_get_time();
    
    if (dirty_ && nowSec > 1577836800LL && SdCard::instance().isMounted()) {
        if (monoUs - lastSaveMonoUs_ >= HISTORY_SAVE_INTERVAL_US) {
            shouldSave = true;
            lastSaveMonoUs_ = monoUs;
        }
    }
    xSemaphoreGive(mutex_);

    if (shouldSave) {
        saveToSdCard();
    }
}

void HistoryStore::updateTimeOfDay(const struct tm& value) {
    // Handled in WidgetManager
}

void HistoryStore::updateDate(const struct tm& value) {
    // Handled in WidgetManager
}

void HistoryStore::updateDateTime(const struct tm& value) {
    // Handled in WidgetManager
}

void HistoryStore::clearAll() {
    xSemaphoreTake(mutex_, portMAX_DELAY);
    for (size_t i = 0; i < series_.size(); ++i) {
        HistorySeries& series = series_[i];
        if (!series.active) continue;
        series.fine.clear();
        series.coarse.clear();
        series.hasLatest = false;
        series.latestValue = 0.0f;
        series.latestTs = 0;
        series.lastFineSampleTs = 0;
        series.lastCoarseSampleTs = 0;
    }
    xSemaphoreGive(mutex_);
}

void HistoryStore::saveToSdCard() {
    if (!SdCard::instance().isMounted()) return;
    
    if (now() < 1577836800LL) return;

    FILE* f = fopen(HISTORY_FILE, "wb");
    if (!f) {
        ESP_LOGW(TAG, "Failed to open history file for writing");
        return;
    }

    xSemaphoreTake(mutex_, portMAX_DELAY);
    HistoryFileHeader header = {};
    header.magic = HISTORY_MAGIC;
    header.version = HISTORY_VERSION;
    uint16_t activeCount = 0;
    for (const auto& series : series_) {
        if (series.active) activeCount++;
    }
    header.seriesCount = activeCount;
    header.fineCapacity = HISTORY_FINE_CAP;
    header.coarseCapacity = HISTORY_COARSE_CAP;
    header.fineInterval = HISTORY_FINE_INTERVAL;
    header.coarseInterval = HISTORY_COARSE_INTERVAL;
    xSemaphoreGive(mutex_);

    if (fwrite(&header, sizeof(header), 1, f) != 1) {
        fclose(f);
        ESP_LOGW(TAG, "Failed to write history header");
        return;
    }

    size_t seriesDataSize = sizeof(HistoryFileSeriesHeader) + 
                            sizeof(HistoryPoint) * (HISTORY_FINE_CAP + HISTORY_COARSE_CAP);
    uint8_t* tempBuf = (uint8_t*)malloc(seriesDataSize);
    if (!tempBuf) {
        fclose(f);
        ESP_LOGE(TAG, "Failed to allocate temp buffer for save");
        return;
    }

    for (size_t i = 0; i < series_.size(); ++i) {
        xSemaphoreTake(mutex_, portMAX_DELAY);
        const HistorySeries& series = series_[i];
        if (!series.active) {
            xSemaphoreGive(mutex_);
            continue;
        }
        
        HistoryFileSeriesHeader* sh = (HistoryFileSeriesHeader*)tempBuf;
        sh->groupAddr = series.key.addr;
        sh->source = static_cast<uint8_t>(series.key.source);
        sh->fineCount = static_cast<uint16_t>(series.fine.count);
        sh->fineHead = static_cast<uint16_t>(series.fine.head);
        sh->coarseCount = static_cast<uint16_t>(series.coarse.count);
        sh->coarseHead = static_cast<uint16_t>(series.coarse.head);
        sh->hasLatest = series.hasLatest ? 1 : 0;
        sh->latestTs = series.latestTs;
        sh->latestValue = series.latestValue;

        uint8_t* dataPtr = tempBuf + sizeof(HistoryFileSeriesHeader);
        memcpy(dataPtr, series.fine.points.data(), sizeof(HistoryPoint) * HISTORY_FINE_CAP);
        dataPtr += sizeof(HistoryPoint) * HISTORY_FINE_CAP;
        memcpy(dataPtr, series.coarse.points.data(), sizeof(HistoryPoint) * HISTORY_COARSE_CAP);
        
        xSemaphoreGive(mutex_);

        if (fwrite(tempBuf, 1, seriesDataSize, f) != seriesDataSize) {
            ESP_LOGW(TAG, "Failed to write series data");
            break;
        }
    }
    
    free(tempBuf);

    xSemaphoreTake(mutex_, portMAX_DELAY);
    dirty_ = false;
    xSemaphoreGive(mutex_);

    fclose(f);
    ESP_LOGI(TAG, "History saved (%d series)", static_cast<int>(activeCount));
}

void HistoryStore::loadFromSdCard() {
    if (!SdCard::instance().isMounted()) return;

    FILE* f = fopen(HISTORY_FILE, "rb");
    if (!f) return;

    HistoryFileHeader header = {};
    if (fread(&header, sizeof(header), 1, f) != 1) {
        fclose(f);
        return;
    }

    if (header.magic != HISTORY_MAGIC || header.version != HISTORY_VERSION) {
        fclose(f);
        ESP_LOGW(TAG, "History header mismatch");
        return;
    }

    if (header.fineCapacity != HISTORY_FINE_CAP ||
        header.coarseCapacity != HISTORY_COARSE_CAP ||
        header.fineInterval != HISTORY_FINE_INTERVAL ||
        header.coarseInterval != HISTORY_COARSE_INTERVAL) {
        fclose(f);
        ESP_LOGW(TAG, "History config mismatch");
        return;
    }

    xSemaphoreTake(mutex_, portMAX_DELAY);
    for (uint16_t i = 0; i < header.seriesCount; ++i) {
        HistoryFileSeriesHeader sh = {};
        if (fread(&sh, sizeof(sh), 1, f) != 1) break;

        HistorySeries* series = findSeries(sh.groupAddr, static_cast<TextSource>(sh.source));
        if (series) {
            series->fine.count = sh.fineCount > HISTORY_FINE_CAP ? HISTORY_FINE_CAP : sh.fineCount;
            series->fine.head = sh.fineHead >= HISTORY_FINE_CAP ? 0 : sh.fineHead;
            series->coarse.count = sh.coarseCount > HISTORY_COARSE_CAP ? HISTORY_COARSE_CAP : sh.coarseCount;
            series->coarse.head = sh.coarseHead >= HISTORY_COARSE_CAP ? 0 : sh.coarseHead;
            series->hasLatest = sh.hasLatest != 0;
            series->latestTs = sh.latestTs;
            series->latestValue = sh.latestValue;

            if (fread(series->fine.points.data(), sizeof(HistoryPoint), HISTORY_FINE_CAP, f) != HISTORY_FINE_CAP) break;
            if (fread(series->coarse.points.data(), sizeof(HistoryPoint), HISTORY_COARSE_CAP, f) != HISTORY_COARSE_CAP) break;
        } else {
            fseek(f, sizeof(HistoryPoint) * (HISTORY_FINE_CAP + HISTORY_COARSE_CAP), SEEK_CUR);
        }
    }
    dirty_ = false;
    xSemaphoreGive(mutex_);

    fclose(f);
    ESP_LOGI(TAG, "History loaded");
}
