#include <Arduino.h>
#include "settings/SettingsManager.hpp"
#include "ble/ThermoproBLE.hpp"
#include "ble/ThermoproReading.hpp"
#include "wifi/WiFiManager.hpp"
#include "api/ApiServer.hpp"
#include "display/DisplayConfig.hpp"
#include "display/Dashboard.hpp"

using cyd::ApiServer;
using cyd::CYD_Display;
using cyd::Dashboard;
using cyd::SettingsManager;
using cyd::ThermoproBLE;
using cyd::ThermoproReading;
using cyd::WiFiManager;

namespace {

SettingsManager settings;
ThermoproBLE bleClient;
CYD_Display display;
Dashboard dashboard(display, settings, bleClient);
WiFiManager wifi(settings);
ApiServer apiServer(settings, bleClient);

enum class AppState {
    Init,
    SetupAP,
    Connecting,
    Running,
    Error
};

AppState appState = AppState::Init;
unsigned long lastHeapLog = 0;
unsigned long lastDashboardUpdate = 0;
unsigned long lastBleStartAttempt = 0;

void requestReboot() {
    delay(500);
    ESP.restart();
}

void onWifiModeChange(bool apMode) {
    apiServer.setSetupMode(apMode);
    if (apMode) {
        appState = AppState::SetupAP;
        dashboard.showMessage("Setup Mode", "Connect to AP:", "CYD-ThermoPro-Setup");
    } else {
        appState = AppState::Connecting;
    }
}

} // namespace

void setup() {
    Serial.begin(115200);
    while (!Serial && millis() < 2000) {
        ;
    }

    Serial.println("\n=== CYD ThermoPro Bridge ===");
    Serial.printf("ESP32 chip model: %s, revision: %d\n", ESP.getChipModel(), ESP.getChipRevision());
    Serial.printf("Flash size: %d MB\n", ESP.getFlashChipSize() / (1024 * 1024));
    Serial.printf("Free heap at boot: %d bytes\n", ESP.getFreeHeap());

    if (!settings.begin()) {
        Serial.println("ERROR: failed to initialize settings storage");
    } else {
        Serial.println("Settings storage initialized");
        Serial.printf("Device ID: %s\n", settings.getDeviceId().c_str());
        Serial.printf("ThermoPro MAC: %s\n", settings.getThermoproMac().c_str());
    }

    // Initialize display early so we can show status on the screen.
    dashboard.begin();

    // Register callbacks
    wifi.onModeChange(onWifiModeChange);
    apiServer.onRebootRequested(requestReboot);

    // Initialize BLE
    if (!settings.hasThermoproMac()) {
        Serial.println("No ThermoPro MAC configured — starting setup AP");
        wifi.enterSetupMode();
    } else {
        bleClient.setAddress(settings.getThermoproMac());
        bleClient.init("cyd-thermopro");
        wifi.begin();
    }

    // Start the HTTP API last, after WiFi/BLE mode is decided.
    apiServer.begin(80);
    Serial.println("Setup complete — entering main loop");
}

void loop() {
    wifi.update();
    apiServer.update();
    bleClient.update();

    // Heartbeat logging
    if (millis() - lastHeapLog >= 10000) {
        lastHeapLog = millis();
        Serial.printf("[heartbeat] state=%d free_heap=%d wifi=%s ble=%s/%s\n",
            static_cast<int>(appState),
            ESP.getFreeHeap(),
            wifi.isConnected() ? "connected" : "not-connected",
            bleClient.getReading().connected ? "connected" : "disconnected",
            bleClient.getStateName());
    }

    switch (appState) {
        case AppState::Init:
            // Transitioned via onWifiModeChange callback.
            break;

        case AppState::SetupAP:
            if (wifi.isConnected()) {
                // User submitted setup form and device rebooted into station mode.
                appState = AppState::Connecting;
            }
            break;

        case AppState::Connecting:
            if (wifi.isConnected()) {
                Serial.println("WiFi connected — starting BLE and dashboard");
                appState = AppState::Running;
                dashboard.begin();

                if (settings.hasThermoproMac()) {
                    bleClient.setAddress(settings.getThermoproMac());
                    bleClient.startConnect();
                }
            } else if (wifi.isApMode()) {
                Serial.println("WiFi failed — back in setup mode");
                appState = AppState::SetupAP;
            }
            break;

        case AppState::Running: {
            if (!wifi.isConnected()) {
                Serial.println("WiFi lost during run");
                appState = AppState::Connecting;
                break;
            }

            if (!settings.hasThermoproMac()) {
                dashboard.showMessage("No MAC configured", "Open setup at", wifi.getIpAddress());
                break;
            }

            // Proactively (re)start BLE connect if it is not doing anything.
            if (!bleClient.getReading().connected) {
                const char* state = bleClient.getStateName();
                if (strcmp(state, "idle") == 0 || strcmp(state, "reconnecting") == 0) {
                    if (millis() - lastBleStartAttempt >= 5000) {
                        lastBleStartAttempt = millis();
                        Serial.printf("Main: requesting BLE connect to %s (state=%s)\n",
                            settings.getThermoproMac().c_str(), state);
                        bleClient.setAddress(settings.getThermoproMac());
                        if (!bleClient.startConnect()) {
                            Serial.println("Main: BLE startConnect returned false");
                        }
                    }
                }
            }

            // Dashboard refresh
            if (millis() - lastDashboardUpdate >= 1000) {
                lastDashboardUpdate = millis();
                dashboard.update();
            }

            // Touch settings button
            if (dashboard.checkTouch()) {
                Serial.println("Settings button touched — entering setup AP");
                wifi.enterSetupMode();
            }
            break;
        }

        case AppState::Error:
            dashboard.showMessage("Error", "Check serial log", "");
            delay(5000);
            ESP.restart();
            break;
    }

    delay(1); // yield to RTOS tasks
}
