#include "BLE_UpdateService.h"
#include "WiFi.h"
#include "my_wifi.h"
#include "global.h"
#include "AppUpgrade.h" 
#include "AppVersion.h"
#include "BleSettings.h"

static const char *tag = "BLE_UpdateService";

//#define UPGRADE_SERVICE_UUID "abcdef01-2345-6789-1234-56789abcdef0"
//#define UPGRADE_CHARACTERISTIC1_UUID "abcdef01-2345-6789-1234-56789abcdef1"
//#define UPGRADE_CHARACTERISTIC2_UUID "abcdef02-2345-6789-1234-56789abcdef1"

NimBLEService *pUpgradeService = nullptr;
NimBLECharacteristic *pUpgradeCharacteristic1 = nullptr;
NimBLECharacteristic *pUpgradeCharacteristic2 = nullptr;

enum WIFI_STAT : byte { WIFI_DISCONNECTED=0, WIFI_BAD_CREDS=1, WIFI_NO_AP=2, WIFI_CONNECTED=3 }; 

struct updateStatus {
    WIFI_STAT wifiStatus = WIFI_DISCONNECTED;
    bool wifiOnline = false;
    byte wifiIP[4] = {0, 0, 0, 0};
    byte currVersion[3] = {FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH};
    byte newVersion[3] = {0, 0, 0};
    char wifiSSID[20] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
}updatePacket;


// Class for handling characteristic events
class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
    
    void onWrite(NimBLECharacteristic *pCharacteristic) override {
        std::string value = pCharacteristic->getValue();
        ESP_LOGD(tag, "Upgrade Char written with value: %s", value.c_str());

        if (value.compare(0, 12, "wifi-connect") == 0) { // Update WiFi credentials
            // Expecting: "wifi-connect:{\"ssid\":...,\"pass\":...}"
            size_t jsonStart = (value.size() > 12 && value[12] == ':') ? 13 : 12;
            if (value.size() <= jsonStart) {
                ESP_LOGW(tag, "wifi-connect command missing JSON payload");
                return;
            }

            JsonDocument doc;
            DeserializationError err = deserializeJson(doc, value.substr(jsonStart));
            if (err) {
                ESP_LOGW(tag, "JSON parse error for wifi-connect: %s", err.c_str());
                return;
            }

            JsonObject wifiJson = doc.as<JsonObject>();
            String ssid = wifiJson["ssid"].as<String>();
            String pass = wifiJson["pass"].as<String>();
            if (ssid.length() == 0) {
                ESP_LOGW(tag, "wifi-connect missing ssid");
                return;
            }
            ESP_LOGI(tag, "WiFi connect requested: ssid='%s', pass len=%u", ssid.c_str(), (unsigned)pass.length());

            bool started = StartWifiConnectTask(ssid, pass);
            if (started) {
                updatePacket.wifiStatus = WIFI_DISCONNECTED;
                updatePacket.wifiOnline = false;
                updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0;
                strncpy(updatePacket.wifiSSID, ssid.c_str(), sizeof(updatePacket.wifiSSID) - 1);
            } else {
                ESP_LOGW(tag, "Failed to start WiFi connection task");
            }
        }
        else if (value.compare("version-check") == 0) { // Check if new version is available
            ESP_LOGI(tag, "Version check command received: newVersion=%d.%d.%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch());
            if(updatePacket.newVersion[0] == 0){
                startVersionCheckTask(); // start the task and done
            }else{
                ESP_LOGI(tag, "Version already checked");
            }
        }
        else if (value.compare("upgrade-start") == 0) { // Start OTA update
            ESP_LOGI(tag, "Start OTA update command received");
            startFirmwareUpdateTask(nullptr); // start the task
        }
        else if (value.compare("rename-device") == 0) { // Start renaming device
            ESP_LOGI(tag, "Start renane device command received");        
        }
        else {
            ESP_LOGW(tag, "Unknown command received: %s", value.c_str());
        }
    }

    void onRead(NimBLECharacteristic *pCharacteristic) override {
        updatePacket.wifiOnline = InternetAvailable;
        if (WiFi.status() == WL_CONNECTED) {
            updatePacket.wifiStatus = WIFI_CONNECTED;
            IPAddress ip = WiFi.localIP();
            updatePacket.wifiIP[0] = ip[0];
            updatePacket.wifiIP[1] = ip[1];
            updatePacket.wifiIP[2] = ip[2];
            updatePacket.wifiIP[3] = ip[3];
            strncpy(updatePacket.wifiSSID, WiFi.SSID().c_str(), sizeof(updatePacket.wifiSSID) - 1);
            ESP_LOGI(tag, "WiFi packet: SSID='%s'", updatePacket.wifiSSID);
        } else {
            updatePacket.wifiStatus = WIFI_DISCONNECTED;
            updatePacket.wifiIP[0] = 0;
            updatePacket.wifiIP[1] = 0;
            updatePacket.wifiIP[2] = 0;
            updatePacket.wifiIP[3] = 0;
            memset(updatePacket.wifiSSID, 0, sizeof(updatePacket.wifiSSID));
        }

        //update version
        if(otaVersion.major() != 0){
            ESP_LOGI(tag, "Updated new version: major=%d, minor=%d, patch=%d", otaVersion.major(), otaVersion.minor(), otaVersion.patch());
            updatePacket.newVersion[0] = otaVersion.major();
            updatePacket.newVersion[1] = otaVersion.minor();
            updatePacket.newVersion[2] = otaVersion.patch();
        }

        // Only populate the control characteristic with the status packet
        if (pCharacteristic == pUpgradeCharacteristic1) {
            pCharacteristic->setValue(reinterpret_cast<uint8_t*>(&updatePacket), sizeof(updatePacket));
            ESP_LOGI(tag, "Upgrade status read");
        }
    }
};


void bleUpgrade_send_message(String s){
    if(pUpgradeCharacteristic2){
        if (s.length() == 0) {
            return;
        }
        // Set value and notify only if there are subscribers to avoid unnecessary work
        pUpgradeCharacteristic2->setValue(s.c_str());
        if (pUpgradeCharacteristic2->getSubscribedCount() > 0) {
            pUpgradeCharacteristic2->notify();
        }
    }
}


void Init_UpgradeBLEService(NimBLEServer *pServer){

    // Create Upgrade BLE Service
    pUpgradeService= pServer->createService( BTUpgradeServiceUUID.c_str() );

    pUpgradeCharacteristic1 = pUpgradeService->createCharacteristic(
        BTUpgradeCharacteristic1UUID.c_str(),
        NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
    );

    // Register the callback with the characteristic
    pUpgradeCharacteristic1->setCallbacks(new UpgradeChar_Callbacks());
    ESP_LOGI(tag, "Upgrade callback registered!");


    pUpgradeCharacteristic2 = pUpgradeService->createCharacteristic(
        BTUpgradeCharacteristic2UUID.c_str(),
        NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY
    );

    // Register the callback with the characteristic
    pUpgradeCharacteristic2->setCallbacks(new UpgradeChar_Callbacks());
    ESP_LOGI(tag, "Upgrade callback registered!");

    pUpgradeService->start();

    NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
    pAdvertising->addServiceUUID( BTUpgradeServiceUUID.c_str() ); // Advertise service UUID


}

