#include "KnxWorker.hpp"
#include "WidgetManager.hpp"
#include "esp32_idf_platform.h"
#include "knx_facade.h"
#include "knx/bau07B0.h"
#include "knx/group_object.h"
#include "knx/group_object_table_object.h"
#include "knx/dpt.h"
#include "esp_log.h"
#include <esp_timer.h>
#include "esp_system.h"
#include "nvs.h"
#include <cstdio>
#include <cstring>

#define TAG "KNXWORKER"
#define MASK_VERSION   0x07B0

// Set to true to enable raw UART debug mode (bypasses KNX library)
#define UART_DEBUG_MODE false

Esp32IdfPlatform knxPlatform(UART_NUM_1); // Use UART_NUM_1, change if needed
Bau07B0 knxBau(knxPlatform);
KnxFacade<Esp32IdfPlatform, Bau07B0> knx(knxBau);

KnxWorker::KnxWorker() {}

namespace {
constexpr char kKnxNvsNamespace[] = "knx";
constexpr char kKnxSerialKey[] = "serial_bau";
constexpr uint8_t kKnxHardwareType[6] = {0x00, 0x00, 0xAB, 0xCE, 0x04, 0x00};
constexpr uint16_t kKnxHardwareVersion = 1;

bool loadKnxBauNumber(uint32_t& outValue) {
    nvs_handle_t handle;
    esp_err_t err = nvs_open(kKnxNvsNamespace, NVS_READONLY, &handle);
    if (err != ESP_OK) {
        return false;
    }

    uint32_t value = 0;
    err = nvs_get_u32(handle, kKnxSerialKey, &value);
    nvs_close(handle);
    if (err != ESP_OK) {
        return false;
    }

    outValue = value;
    return true;
}

bool saveKnxBauNumber(uint32_t value) {
    nvs_handle_t handle;
    esp_err_t err = nvs_open(kKnxNvsNamespace, NVS_READWRITE, &handle);
    if (err != ESP_OK) {
        return false;
    }

    err = nvs_set_u32(handle, kKnxSerialKey, value);
    if (err == ESP_OK) {
        err = nvs_commit(handle);
    }
    nvs_close(handle);
    return err == ESP_OK;
}

uint32_t generateRandomBauNumber() {
    uint32_t value = esp_random();
    if (value == 0) {
        value = 1;
    }
    return value;
}

uint16_t resolveGroupAddress(uint16_t asap) {
    if (!knxBau.configured()) {
        return 0;
    }
    int32_t tsap = knxBau.associationTable().translateAsap(asap);
    if (tsap < 0) {
        return 0;
    }
    return knxBau.addressTable().getGroupAddress(static_cast<uint16_t>(tsap));
}
} // namespace


void KnxWorker::init() {
     ESP_LOGI(TAG, "INIT");

    knxPlatform.knxUartPins(48, 53); // Set RX=48, TX=53
    knxPlatform.knxUartBaudRate(19200);
    knxPlatform.setupUart();

#if !UART_DEBUG_MODE
    knx.bau().deviceObject().hardwareType(kKnxHardwareType);
    knx.bau().deviceObject().version(kKnxHardwareVersion);

    knx.readMemory();

    uint32_t bauNumberOverride = 0;
    if (loadKnxBauNumber(bauNumberOverride)) {
        knx.bau().deviceObject().manufacturerId(0x00FA);
        knx.bau().deviceObject().bauNumber(bauNumberOverride);
        ESP_LOGI(TAG, "Applied KNX serial override: %04X%08lX", 0x00FA, (unsigned long)bauNumberOverride);
    }

    // Register callbacks for all group objects to forward updates to the GUI
    size_t goCount = getGroupObjectCount();
    if (goCount == 0) {
        ESP_LOGW(TAG, "No KNX group objects configured; skipping callbacks");
    } else {
        for (size_t i = 1; i <= goCount; i++) {
            GroupObject& go = knx.getGroupObject(i);
            go.callback([](GroupObject& go) {
                uint16_t groupAddr = resolveGroupAddress(go.asap());
                if (groupAddr == 0) {
                    return;
                }

                KNXValue switchValue = false;
                if (go.tryValue(switchValue, DPT_Switch)) {
                    WidgetManager::instance().onKnxSwitch(groupAddr, static_cast<bool>(switchValue));
                }

                KNXValue tempValue = 0.0f;
                if (go.tryValue(tempValue, DPT_Value_Temp)) {
                    WidgetManager::instance().onKnxValue(groupAddr, static_cast<float>(tempValue),
                        TextSource::KNX_DPT_TEMP);
                }

                KNXValue percentValue = 0.0f;
                if (go.tryValue(percentValue, DPT_Scaling)) {
                    WidgetManager::instance().onKnxValue(groupAddr, static_cast<float>(percentValue),
                        TextSource::KNX_DPT_PERCENT);
                }

                KNXValue factorValue = (uint8_t)0;
                if (go.tryValue(factorValue, DPT_DecimalFactor)) {
                    WidgetManager::instance().onKnxValue(groupAddr, static_cast<float>(factorValue),
                        TextSource::KNX_DPT_DECIMALFACTOR);
                }

                KNXValue powerValue = 0.0f;
                if (go.tryValue(powerValue, DPT_Value_Power)) {
                    WidgetManager::instance().onKnxValue(groupAddr, static_cast<float>(powerValue),
                        TextSource::KNX_DPT_POWER);
                }

                KNXValue energyValue = (int32_t)0;
                if (go.tryValue(energyValue, DPT_ActiveEnergy_kWh)) {
                    WidgetManager::instance().onKnxValue(groupAddr, static_cast<float>(energyValue),
                        TextSource::KNX_DPT_ENERGY);
                }

                struct tm timeTm = {};
                KNXValue timeValue(timeTm);
                if (go.tryValue(timeValue, DPT_TimeOfDay)) {
                    WidgetManager::instance().onKnxTime(groupAddr, static_cast<struct tm>(timeValue),
                        KnxTimeType::TIME);
                }

                struct tm dateTm = {};
                KNXValue dateValue(dateTm);
                if (go.tryValue(dateValue, DPT_Date)) {
                    WidgetManager::instance().onKnxTime(groupAddr, static_cast<struct tm>(dateValue),
                        KnxTimeType::DATE);
                }

                struct tm dateTimeTm = {};
                KNXValue dateTimeValue(dateTimeTm);
                if (go.tryValue(dateTimeValue, DPT_DateTime)) {
                    WidgetManager::instance().onKnxTime(groupAddr, static_cast<struct tm>(dateTimeValue),
                        KnxTimeType::DATETIME);
                }

                KNXValue textValue = "";
                if (go.tryValue(textValue, DPT_String_8859_1) ||
                    go.tryValue(textValue, DPT_String_ASCII)) {
                    const char* raw = static_cast<const char*>(textValue);
                    size_t maxLen = go.valueSize();
                    if (maxLen > 14) {
                        maxLen = 14;
                    }
                    size_t len = strnlen(raw, maxLen);
                    char buf[15];
                    memcpy(buf, raw, len);
                    buf[len] = '\0';
                    WidgetManager::instance().onKnxText(groupAddr, buf);
                }
            });
        }
    }

    knx.start();
    ESP_LOGI(TAG, "FINISH");
#else
    ESP_LOGI(TAG, "UART DEBUG MODE - KNX library disabled");
    ESP_LOGI(TAG, "Sending U_STATE_REQ (0x02) to NCN5130...");
    knxPlatform.writeUart(0x02); // U_STATE_REQ command
    ESP_LOGI(TAG, "Waiting for response...");
#endif
}

static uint32_t lastStateReq = 0;

void KnxWorker::toggleProgMode() {
#if !UART_DEBUG_MODE
    knx.toggleProgMode();
#endif
}

bool KnxWorker::getProgMode() {
#if !UART_DEBUG_MODE
    return knx.progMode();
#else
    return false;
#endif
}

void KnxWorker::setProgMode(bool enabled) {
#if !UART_DEBUG_MODE
    knx.progMode(enabled);
#else
    (void)enabled;
#endif
}

void KnxWorker::clearSettings() {
#if !UART_DEBUG_MODE
    if (knxResetState_ == 0) {
        uint32_t bauNumber = generateRandomBauNumber();
        bool stored = saveKnxBauNumber(bauNumber);
        knx.bau().deviceObject().manufacturerId(0x00FA);
        knx.bau().deviceObject().bauNumber(bauNumber);
        if (stored) {
            ESP_LOGI(TAG, "KNX serial randomized to %04X%08lX", 0x00FA, (unsigned long)bauNumber);
        } else {
            ESP_LOGW(TAG, "Failed to persist randomized KNX serial");
        }
        knxResetState_ = 1;
    }
#endif
}

#include "HistoryStore.hpp"

// ... imports ...

void KnxWorker::loop() {
    // Check for auto-save (handled by HistoryStore logic)
    HistoryStore::instance().performAutoSave();

#if UART_DEBUG_MODE
    // Periodically send U_STATE_REQ to test TX direction
    uint32_t now = millis();
    if (now - lastStateReq > 2000) {
        lastStateReq = now;
        ESP_LOGI(TAG, "TX: Sending U_STATE_REQ (0x02)");
        knxPlatform.writeUart(0x02);
    }

    // Raw UART debug - read and print all incoming bytes
    int available = knxPlatform.uartAvailable();
    if (available > 0) {
        ESP_LOGI(TAG, "UART RX: %d bytes available", available);
        char hexbuf[128];
        int pos = 0;
        while (knxPlatform.uartAvailable() > 0 && pos < 120) {
            int byte = knxPlatform.readUart();
            if (byte >= 0) {
                pos += snprintf(hexbuf + pos, sizeof(hexbuf) - pos, "%02X ", byte);
            }
        }
        if (pos > 0) {
            ESP_LOGI(TAG, "Data: %s", hexbuf);
        }
    }
#else
    if (knxResetState_ != 0) {
        uint32_t nowMs = (uint32_t)(esp_timer_get_time() / 1000);
        if (knxResetState_ == 1) {
            knx.bau().memory().clearMemory();
            knxResetAtMs_ = nowMs + 300;
            knxResetState_ = 2;
        } else if (knxResetState_ == 2) {
            if ((int32_t)(nowMs - knxResetAtMs_) >= 0) {
                knxResetState_ = 0;
                knx.bau().platform().restart();
            }
        }
    }
    knx.loop();
#endif
}

size_t KnxWorker::getGroupObjectCount() {
#if !UART_DEBUG_MODE
    // Get the group object table from BAU
    GroupObjectTableObject& goTable = knxBau.groupObjectTable();
    return goTable.entryCount();
#else
    return 0;
#endif
}

bool KnxWorker::getGroupObjectInfo(size_t index, KnxGroupObjectInfo& info) {
#if !UART_DEBUG_MODE
    GroupObjectTableObject& goTable = knxBau.groupObjectTable();

    if (index == 0 || index > goTable.entryCount()) {
        return false;
    }

    // Get group object (1-based index)
    GroupObject& go = knx.getGroupObject(index);

    info.goIndex = index;
    info.dptMain = 0;
    info.dptSub = 0;
    info.commFlag = go.commFlag();
    info.readFlag = go.readEnable();
    info.writeFlag = go.writeEnable();
    info.transmitFlag = go.transmitEnable();

    // Resolve the primary group address via association/address tables
    info.groupAddress = resolveGroupAddress(static_cast<uint16_t>(index));

    return true;
#else
    (void)index;
    (void)info;
    return false;
#endif
}

bool KnxWorker::sendSwitch(uint16_t groupAddr, bool value) {
#if !UART_DEBUG_MODE
    if (!knxBau.configured()) {
        ESP_LOGW(TAG, "sendSwitch: KNX not configured");
        return false;
    }

    // Find the GroupObject index for this group address
    size_t goCount = getGroupObjectCount();
    for (size_t i = 1; i <= goCount; i++) {
        uint16_t addr = resolveGroupAddress(static_cast<uint16_t>(i));
        if (addr == groupAddr) {
            GroupObject& go = knx.getGroupObject(i);

            // Check if this GO can transmit
            if (!go.transmitEnable()) {
                ESP_LOGW(TAG, "sendSwitch: GO %d cannot transmit", (int)i);
                return false;
            }

            // Set value and trigger send
            KNXValue knxVal = value;
            if (go.value(knxVal, DPT_Switch)) {
                ESP_LOGI(TAG, "sendSwitch: GA=%d GO=%d value=%d", groupAddr, (int)i, value);
                return true;
            } else {
                ESP_LOGW(TAG, "sendSwitch: Failed to set value for GO %d", (int)i);
                return false;
            }
        }
    }

    ESP_LOGW(TAG, "sendSwitch: No GO found for GA=%d", groupAddr);
    return false;
#else
    (void)groupAddr;
    (void)value;
    return false;
#endif
}

void KnxWorker::formatGroupAddress(uint16_t addr, char* buf, size_t bufSize) {
    // Format: main/middle/sub (5/3/8 bit)
    uint8_t main = (addr >> 11) & 0x1F;
    uint8_t middle = (addr >> 8) & 0x07;
    uint8_t sub = addr & 0xFF;
    snprintf(buf, bufSize, "%d/%d/%d", main, middle, sub);
}
