#include "diagnostics_manager.h"
#include "config.h"
#include "StepperManager.h"
#include "AutomationManager.h"
#include "async_network.h"
#include "mqtt_manager.h"
// Only include task_scheduler if stats are enabled (header may not exist)
#if defined(TASK_STATS_ENABLED) && TASK_STATS_ENABLED
#include "task_scheduler.h"
#endif
#include <esp_system.h>
#include <esp_heap_caps.h>
#include <esp_chip_info.h>
#include <esp_timer.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

DiagnosticsManager diagnosticsManager;

DiagnosticsManager::DiagnosticsManager()
    : _wifiDisconnectCount(0), _mqttReconnectCount(0), _stepperMoveCount(0),
      _motorEnableDisableCycles(0), _touchTotalCount(0), _touchFalseCount(0),
      _publishSuccessCount(0), _publishFailCount(0), _errorCount(0) {}

// ============================================================================
// Event Logging
// ============================================================================

void DiagnosticsManager::logWiFiEvent(const char* type, uint8_t reason, int8_t rssi) {
  WiFiEvent evt = {};
  evt.timestamp = millis();
  evt.reason = reason;
  evt.rssi = rssi;
  strlcpy(evt.type, type, sizeof(evt.type));
  _wifiEvents.push(evt);
}

void DiagnosticsManager::logMqttEvent(const char* type, int8_t stateCode) {
  MqttEvent evt = {};
  evt.timestamp = millis();
  evt.stateCode = stateCode;
  strlcpy(evt.type, type, sizeof(evt.type));
  _mqttEvents.push(evt);
}

void DiagnosticsManager::logStepperEvent(const char* type, int32_t startPos,
    int32_t endPos, int32_t targetPos, unsigned long durationMs, uint16_t speed) {
  StepperEvent evt = {};
  evt.timestamp = millis();
  evt.durationMs = durationMs;
  evt.startPos = startPos;
  evt.endPos = endPos;
  evt.targetPos = targetPos;
  evt.speedUsed = speed;
  strlcpy(evt.type, type, sizeof(evt.type));
  _stepperEvents.push(evt);
}

void DiagnosticsManager::logTouchEvent(const char* action, uint16_t durationMs) {
  TouchEvent evt = {};
  evt.timestamp = millis();
  evt.durationMs = durationMs;
  strlcpy(evt.action, action, sizeof(evt.action));
  _touchEvents.push(evt);
}

void DiagnosticsManager::logGeneralEvent(const char* category, const char* message) {
  GeneralEvent evt = {};
  evt.timestamp = millis();
  strlcpy(evt.category, category, sizeof(evt.category));
  strlcpy(evt.message, message, sizeof(evt.message));
  _generalEvents.push(evt);
}

void DiagnosticsManager::logErrorEvent(const char* module, const char* message) {
  ErrorEvent evt = {};
  evt.timestamp = millis();
  strlcpy(evt.module, module, sizeof(evt.module));
  strlcpy(evt.message, message, sizeof(evt.message));
  _errorEvents.push(evt);
  _errorCount++;
  Serial.printf("[DIAG:ERROR] [%s] %s\n", module, message);
}

// ============================================================================
// Reset
// ============================================================================

void DiagnosticsManager::resetAll() {
  _wifiEvents.clear();
  _mqttEvents.clear();
  _stepperEvents.clear();
  _touchEvents.clear();
  _generalEvents.clear();
  _errorEvents.clear();
  _wifiDisconnectCount = 0;
  _mqttReconnectCount = 0;
  _stepperMoveCount = 0;
  _motorEnableDisableCycles = 0;
  _touchTotalCount = 0;
  _touchFalseCount = 0;
  _publishSuccessCount = 0;
  _publishFailCount = 0;
  _errorCount = 0;
  Serial.println(F("[Diag] All counters and logs reset"));
}

// ============================================================================
// Full Diagnostics Build
// ============================================================================

void DiagnosticsManager::buildDiagnosticsJson(JsonDocument& doc, bool verbose) {
  doc["deviceType"] = "stepper";
  doc["diagVersion"] = 1;
  doc["timestamp"] = millis() / 1000;

  // Use a local time string if available
  time_t now = time(nullptr);
  if (now > 1000000000) {
    struct tm* ti = localtime(&now);
    if (ti) {
      char timeBuf[20];
      snprintf(timeBuf, sizeof(timeBuf), "%04d-%02d-%02d %02d:%02d:%02d",
               ti->tm_year + 1900, ti->tm_mon + 1, ti->tm_mday,
               ti->tm_hour, ti->tm_min, ti->tm_sec);
      doc["localTime"] = String(timeBuf);
    }
  }

  // --- System Health ---
  JsonObject sys = doc["system"].to<JsonObject>();
  buildSystemHealth(sys);

  // --- WiFi ---
  JsonObject wifi = doc["wifi"].to<JsonObject>();
  buildWiFiDiagnostics(wifi);

  // --- MQTT ---
  JsonObject mqtt = doc["mqtt"].to<JsonObject>();
  buildMqttDiagnostics(mqtt);

  // --- Stepper Motor ---
  JsonObject stepper = doc["stepper"].to<JsonObject>();
  buildStepperDiagnostics(stepper);

  // --- Touch Sensor ---
  JsonObject touch = doc["touch"].to<JsonObject>();
  buildTouchDiagnostics(touch);

  // --- Automation ---
  JsonObject automation = doc["automation"].to<JsonObject>();
  buildAutomationDiagnostics(automation);

  // --- Task Scheduler ---
#if TASK_STATS_ENABLED
  JsonObject tasks = doc["tasks"].to<JsonObject>();
  buildTaskSchedulerStats(tasks);
#endif

  JsonObject errs = doc["errors"].to<JsonObject>();
  errs["total"] = _errorCount;
  JsonArray errList = errs["history"].to<JsonArray>();
  _errorEvents.toJson(errList, 8);

  // --- Event Log ---
  if (verbose) {
    JsonArray events = doc["events"].to<JsonArray>();
    _generalEvents.toJson(events, 20);
  }
}

// ============================================================================
// System Health
// ============================================================================

void DiagnosticsManager::buildSystemHealth(JsonObject& sys) {
  sys["freeHeap"] = ESP.getFreeHeap();
  sys["minFreeHeap"] = ESP.getMinFreeHeap();
  sys["heapSize"] = ESP.getHeapSize();
  sys["maxAllocHeap"] = ESP.getMaxAllocHeap();

  // Fragmentation estimate: (1 - maxAllocBlock / freeHeap) * 100
  uint32_t freeH = ESP.getFreeHeap();
  uint32_t maxAlloc = ESP.getMaxAllocHeap();
  if (freeH > 0) {
    sys["fragmentation"] = (int)(100 - (maxAlloc * 100 / freeH));
  }

  // Stack high-water mark for main task
  sys["stackFree"] = uxTaskGetStackHighWaterMark(NULL);

  sys["uptime"] = millis() / 1000;
  sys["uptimeDays"] = millis() / 86400000UL;
  sys["cpuFreqMhz"] = getCpuFrequencyMhz();
  sys["cpuTemp"] = temperatureRead();

  sys["resetReason"] = getResetReasonString();
  sys["firmware"] = FIRMWARE_VERSION;

  // Chip info
  esp_chip_info_t chipInfo;
  esp_chip_info(&chipInfo);
  sys["chipCores"] = chipInfo.cores;
  sys["chipRevision"] = chipInfo.revision;

  // Flash info
  sys["flashSize"] = ESP.getFlashChipSize();
  sys["sketchSize"] = ESP.getSketchSize();
  sys["freeSketchSpace"] = ESP.getFreeSketchSpace();

  // EEPROM/Config stats
  sys["configSize"] = sizeof(ConfigData);
  sys["eepromSize"] = EEPROM_SIZE;
  sys["configVersion"] = CONFIG_VERSION;
}

// ============================================================================
// WiFi Diagnostics
// ============================================================================

void DiagnosticsManager::buildWiFiDiagnostics(JsonObject& wifi) {
  wifi["connected"] = (WiFi.status() == WL_CONNECTED);
  wifi["rssi"] = WiFi.RSSI();
  wifi["ssid"] = WiFi.SSID();
  wifi["channel"] = WiFi.channel();
  wifi["bssid"] = WiFi.BSSIDstr();
  wifi["ip"] = WiFi.localIP().toString();
  wifi["gateway"] = WiFi.gatewayIP().toString();
  wifi["dns"] = WiFi.dnsIP().toString();
  wifi["mac"] = WiFi.macAddress();
  wifi["txPower"] = WiFi.getTxPower();

  wifi["disconnectCount"] = _wifiDisconnectCount;

  // Disconnect history
  JsonArray history = wifi["history"].to<JsonArray>();
  _wifiEvents.toJson(history, 16);
}

// ============================================================================
// MQTT Diagnostics
// ============================================================================

void DiagnosticsManager::buildMqttDiagnostics(JsonObject& mqtt) {
  mqtt["connected"] = mqttManager.isConnected();
  mqtt["enabled"] = mqttManager.isEnabled();
  mqtt["reconnectCount"] = _mqttReconnectCount;
  mqtt["publishSuccess"] = _publishSuccessCount;
  mqtt["publishFail"] = _publishFailCount;
  mqtt["broker"] = config.mqtt_broker;
  mqtt["port"] = config.mqtt_port;

  // MQTT history
  JsonArray history = mqtt["history"].to<JsonArray>();
  _mqttEvents.toJson(history, 16);
}

// ============================================================================
// Stepper Motor Diagnostics
// ============================================================================

void DiagnosticsManager::buildStepperDiagnostics(JsonObject& stepper) {
  stepper["currentPos"] = stepperManager.getCurrentAbsolutePosition();
  stepper["currentPct"] = stepperManager.getCurrentPercentage();
  stepper["targetPct"] = stepperManager.getTargetPercentage();
  stepper["isMoving"] = stepperManager.isMoving();
  stepper["isCalibrated"] = stepperManager.isCalibrated();

  // Calibration
  JsonObject calibration = stepper["calibration"].to<JsonObject>();
  calibration["topPos"] = config.stepper_top_position;
  calibration["bottomPos"] = config.stepper_bottom_position;
  // FIX Bug #12: Use labs() for int32_t portability (abs() is only guaranteed
  // for int, which may be 16-bit on some platforms).
  int32_t range = (int32_t)labs((long)(config.stepper_top_position - config.stepper_bottom_position));
  calibration["rangeSteps"] = range;

  // Speed configuration
  JsonObject speeds = stepper["speeds"].to<JsonObject>();
  speeds["openSpeed"] = config.stepper_open_speed;
  speeds["closeSpeed"] = config.stepper_close_speed;
  speeds["acceleration"] = config.stepper_acceleration;

  // Tension relief config
  stepper["relaxSteps"] = config.stepper_relax_steps;
  stepper["stopDelayMs"] = config.stepper_stop_delay_ms;

  // Cumulative stats
  stepper["totalMoves"] = _stepperMoveCount;
  stepper["motorCycles"] = _motorEnableDisableCycles;

  // Move history
  JsonArray history = stepper["history"].to<JsonArray>();
  _stepperEvents.toJson(history, 16);

  // Stall detection analysis (from history)
  uint32_t stallCount = 0;
  for (size_t i = 0; i < _stepperEvents.count() && i < 24; i++) {
    const StepperEvent& evt = _stepperEvents.get(i);
    if (strcmp(evt.type, "MOVE") == 0) {
      int32_t expected = (int32_t)labs((long)(evt.targetPos - evt.startPos));
      int32_t actual = (int32_t)labs((long)(evt.endPos - evt.startPos));
      // If we missed more than 5% of steps, flag it
      if (expected > 100 && actual < (expected * 95 / 100)) {
        stallCount++;
      }
    }
  }
  stepper["suspectedStalls"] = stallCount;
}

// ============================================================================
// Touch Diagnostics
// ============================================================================

void DiagnosticsManager::buildTouchDiagnostics(JsonObject& touch) {
  touch["totalEvents"] = _touchTotalCount;
  touch["falseTriggersCount"] = _touchFalseCount;

  // False trigger rate
  if (_touchTotalCount > 0) {
    touch["falseTriggerRate"] = (float)_touchFalseCount / _touchTotalCount * 100.0f;
  } else {
    touch["falseTriggerRate"] = 0;
  }

  // History
  JsonArray history = touch["history"].to<JsonArray>();
  _touchEvents.toJson(history, 16);
}

// ============================================================================
// Automation Diagnostics
// ============================================================================

void DiagnosticsManager::buildAutomationDiagnostics(JsonObject& automation) {
  // Rule enabled state
  JsonObject rules = automation["rules"].to<JsonObject>();
  rules["sunset"] = config.sunset_enabled;
  rules["presence"] = config.presence_enabled;
  rules["morning"] = config.morning_enabled;
  rules["night"] = config.night_enabled;
  rules["temperature"] = config.temp_enabled;

  // Linked device status
  JsonObject linked = automation["linkedDevice"].to<JsonObject>();
  linked["configured"] = (strlen(config.linked_device_id) > 0);
  linked["id"] = config.linked_device_id;
  linked["online"] = automationManager.isLinkedDeviceOnline();

  // Timezone
  automation["gmtOffset"] = config.gmt_offset;
  automation["daylightOffset"] = config.daylight_offset;

  // Time info
  if (network.hasTime()) {
    automation["currentHour"] = network.getHour();
    automation["currentMinute"] = network.getMinute();
    automation["timeSync"] = true;
  } else {
    automation["timeSync"] = false;
  }

  // Sunrise/sunset data
  automation["sunrise"] = network.getSunriseTime();
  automation["sunset"] = network.getSunsetTime();
}

// ============================================================================
// Task Scheduler Stats
// ============================================================================

void DiagnosticsManager::buildTaskSchedulerStats(JsonObject& tasks) {
#if TASK_STATS_ENABLED
  tasks["taskCount"] = taskScheduler.getTaskCount();

  JsonArray taskList = tasks["list"].to<JsonArray>();
  for (int i = 0; i < taskScheduler.getTaskCount(); i++) {
    const Task* t = taskScheduler.getTask(i);
    if (t && t->name) {
      JsonObject tObj = taskList.add<JsonObject>();
      tObj["name"] = t->name;
      tObj["interval"] = t->interval;
      tObj["enabled"] = t->enabled;
      tObj["runCount"] = t->runCount;
      tObj["maxRuntime"] = t->maxRuntime;
      tObj["totalRuntime"] = t->totalRuntime;
      if (t->runCount > 0) {
        tObj["avgRuntime"] = t->totalRuntime / t->runCount;
      }
    }
  }
#endif
}

// ============================================================================
// Reset Reason String
// ============================================================================

const char* DiagnosticsManager::getResetReasonString() {
  esp_reset_reason_t reason = esp_reset_reason();
  switch (reason) {
    case ESP_RST_POWERON:  return "Power On";
    case ESP_RST_SW:       return "Software Reset";
    case ESP_RST_PANIC:    return "Exception/Panic";
    case ESP_RST_INT_WDT:  return "Interrupt Watchdog";
    case ESP_RST_TASK_WDT: return "Task Watchdog";
    case ESP_RST_WDT:      return "Other Watchdog";
    case ESP_RST_DEEPSLEEP:return "Deep Sleep Wake";
    case ESP_RST_BROWNOUT: return "Brownout";
    case ESP_RST_SDIO:     return "SDIO";
    default:               return "Unknown";
  }
}
