#include "storage.h"
#include "secrets.h"
#include <esp_mac.h>
#include <cstddef>

StorageManager storage;
ConfigData config;

// Minimum interval between EEPROM writes to reduce flash wear.
// ESP32 flash is rated for ~100K write cycles at the sector level.
static constexpr unsigned long MIN_SAVE_INTERVAL_MS = 2000;

void StorageManager::begin() {
  EEPROM.begin(EEPROM_SIZE);

  // === Power-loss recovery ===
  // Check for interrupted writes during boot. If the write-in-progress flag
  // is still set (DEADBEEF), a previous save() was interrupted by power loss
  // mid-flash-commit. Restore from the backup slot which holds the last
  // known-good config written before the failed save attempt.
  uint32_t writeFlag = 0;
  EEPROM.get(WRITE_FLAG_ADDR, writeFlag);

  if (writeFlag == WRITE_FLAG_VALUE) {
    Serial.println("[Storage] Power loss detected during write!");

    ConfigData backupConfig;
    EEPROM.get(BACKUP_SLOT_ADDR, backupConfig);

    if (backupConfig.magic == CONFIG_MAGIC) {
      uint32_t backupChecksum = calculateChecksum(backupConfig);
      if (backupChecksum == backupConfig.checksum) {
        Serial.println("[Storage] Restoring from backup...");
        EEPROM.put(0, backupConfig);
        uint32_t clearFlag = WRITE_FLAG_CLEAR;
        EEPROM.put(WRITE_FLAG_ADDR, clearFlag);
        EEPROM.commit();
      } else {
        Serial.println("[Storage] Backup checksum invalid — clearing flag");
        uint32_t clearFlag = WRITE_FLAG_CLEAR;
        EEPROM.put(WRITE_FLAG_ADDR, clearFlag);
        EEPROM.commit();
      }
    } else {
      Serial.println("[Storage] Backup magic invalid — clearing flag");
      uint32_t clearFlag = WRITE_FLAG_CLEAR;
      EEPROM.put(WRITE_FLAG_ADDR, clearFlag);
      EEPROM.commit();
    }
  }

  load();
}

struct ConfigDataV4 {
  uint32_t magic;
  uint32_t version;
  uint32_t checksum;
  bool setup_complete;
  int32_t stepper_top_position;
  int32_t stepper_bottom_position;
  int32_t stepper_current_position;
  uint32_t stepper_open_speed;
  char wifi_ssid[33];
  char wifi_pass[65];
  bool twt_enabled;
  bool mqtt_enabled;
  char mqtt_broker[80];
  uint16_t mqtt_port;
  char mqtt_user[48];
  char mqtt_pass[64];
  char mqtt_device_id[16];
  uint32_t stepper_close_speed;
};

uint32_t calculateChecksumV4(const ConfigDataV4 &data) {
  uint32_t sum = 0;
  const uint8_t *ptr = (const uint8_t *)&data;
  size_t offset = offsetof(ConfigDataV4, checksum) + sizeof(uint32_t);
  for (size_t i = offset; i < sizeof(ConfigDataV4); i++) {
    sum += ptr[i];
  }
  return sum;
}

void StorageManager::load() {
  uint32_t magic, version;
  EEPROM.get(0, magic);
  EEPROM.get(4, version);

  if (magic != CONFIG_MAGIC) {
    Serial.println("Invalid config magic. Resetting to defaults.");
    reset();
    return;
  }

  if (version >= 4 && version <= 7) {
    Serial.printf("Migrating config from v%d to v%d...\n", version, CONFIG_VERSION);
    ConfigDataV4 oldCfg;
    
    if (version == 4) {
      EEPROM.get(0, oldCfg);
      if (oldCfg.checksum != calculateChecksumV4(oldCfg)) {
        Serial.println("V4 Config checksum mismatch! Resetting to defaults.");
        reset();
        return;
      }
    } else {
      // V5-V7 migration: struct layout changed (V8 added per-day arrays), reset to defaults.
      Serial.println("Structure layout changed. Resetting to defaults to ensure stability.");
      reset();
      return;
    }

    memset(&config, 0, sizeof(ConfigData));
    config.magic = oldCfg.magic;
    config.version = CONFIG_VERSION;
    config.setup_complete = oldCfg.setup_complete;
    config.stepper_top_position = oldCfg.stepper_top_position;
    config.stepper_bottom_position = oldCfg.stepper_bottom_position;
    config.stepper_current_position = oldCfg.stepper_current_position;
    config.stepper_open_speed = oldCfg.stepper_open_speed;
    config.stepper_close_speed = oldCfg.stepper_close_speed;
    strlcpy(config.wifi_ssid, oldCfg.wifi_ssid, sizeof(config.wifi_ssid));
    strlcpy(config.wifi_pass, oldCfg.wifi_pass, sizeof(config.wifi_pass));
    config.twt_enabled = oldCfg.twt_enabled;
    config.mqtt_enabled = oldCfg.mqtt_enabled;
    strlcpy(config.mqtt_broker, oldCfg.mqtt_broker, sizeof(config.mqtt_broker));
    config.mqtt_port = oldCfg.mqtt_port;
    strlcpy(config.mqtt_user, oldCfg.mqtt_user, sizeof(config.mqtt_user));
    strlcpy(config.mqtt_pass, oldCfg.mqtt_pass, sizeof(config.mqtt_pass));
    strlcpy(config.mqtt_device_id, oldCfg.mqtt_device_id, sizeof(config.mqtt_device_id));
    
    // Initialize new fields
    config.sunset_enabled = false;
    config.presence_enabled = false;
    config.presence_action = 0;
    config.presence_time_filter = 0;
    config.morning_enabled = false;
    config.night_enabled = false;
    config.temp_enabled = false;
    config.morning_days = 0x7F;
    config.night_days = 0x7F;
    for (int i = 0; i < 7; i++) {
      config.morning_time_h[i] = 7;
      config.morning_time_m[i] = 0;
      config.morning_duration[i] = 30;
      config.morning_target[i] = 100;
    }
    memset(config.linked_device_id, 0, sizeof(config.linked_device_id));
    // V9 Defaults
    config.stepper_relax_steps = 128;
    config.stepper_stop_delay_ms = 3000;
    config.stepper_acceleration = 2000;
    // Automation config defaults (safe values — prevent zero-default issues)
    config.motion_timeout = 300;        // 5 minutes (seconds)
    config.presence_open_target = 100;
    config.night_time_h = 22;
    config.night_time_m = 0;
    config.night_target = 0;
    config.temp_threshold = 35;
    config.temp_target = 20;

    save();
    return;
  }

  EEPROM.get(0, config);

  if (config.version == 10) {
    // V10→V12 Migration: Struct changed from unpacked to __attribute__((packed)),
    // plus new V12 fields (boot_crash_count, tz_posix).
    Serial.println("[Storage] Migrating V10 -> V12 (packed struct + safe mode + POSIX TZ)");
    config.gmt_offset = 0;
    config.daylight_offset = 0;
    config.boot_crash_count = 0;
    memset(config.tz_posix, 0, sizeof(config.tz_posix));
    config.version = CONFIG_VERSION;
    _lastSaveTime = 0; // Force immediate save for migration
    save();
    Serial.println("[Storage] Migration V10->V12 complete");
    return;
  }

  if (config.version == 11) {
    // V11→V12 Migration: Add boot crash counter and POSIX timezone.
    // Both new fields are at the end of the struct, so existing V11 data
    // is fully preserved — we only need to initialize the appended fields.
    Serial.println("[Storage] Migrating V11 -> V12 (safe mode + POSIX TZ)");
    config.boot_crash_count = 0;
    memset(config.tz_posix, 0, sizeof(config.tz_posix));
    config.version = CONFIG_VERSION;
    _lastSaveTime = 0; // Force immediate save for migration
    save();
    Serial.println("[Storage] Migration V11->V12 complete");
    return;
  }
  
  if (config.version != CONFIG_VERSION) {
    Serial.println("Invalid config version mismatch. Resetting to defaults.");
    reset();
    return;
  }

  uint32_t savedChecksum = config.checksum;
  if (savedChecksum != calculateChecksum(config)) {
    Serial.println("Config checksum mismatch! Resetting to defaults.");
    reset();
  } else {
    // FIX Bug #11: Seed the dirty-check baseline from the loaded config.
    // Without this, _lastWrittenChecksum stays at 0 after boot, so the first
    // save() always writes to flash even when data is unchanged.
    _lastWrittenChecksum = savedChecksum;

    // Defense-in-depth: force null-termination on string fields.
    // The CRC protects against corruption, but this prevents buffer overreads
    // if a future code path bypasses the checksum check.
    config.tz_posix[sizeof(config.tz_posix) - 1] = '\0';
  }
}

void StorageManager::save() {
  // Throttle writes: skip if called within MIN_SAVE_INTERVAL_MS of last write
  unsigned long now = millis();
  if (_lastSaveTime != 0 && (now - _lastSaveTime) < MIN_SAVE_INTERVAL_MS) {
    _pendingSave = true;  // Mark for deferred save
    return;
  }

  // Dirty check: compute new checksum and skip write if data is unchanged
  uint32_t newChecksum = calculateChecksum(config);
  if (newChecksum == _lastWrittenChecksum && _lastWrittenChecksum != 0) {
    return;  // Data unchanged since last write
  }

  // === Atomic write protocol ===
  // Protects against power loss during EEPROM flash commit.
  // Sequence: backup → flag → write → commit → verify → clear flag.

  config.checksum = newChecksum;

  // STEP 1: Backup current valid primary config (if it has valid magic/checksum)
  ConfigData storedConfig;
  EEPROM.get(0, storedConfig);
  if (storedConfig.magic == CONFIG_MAGIC) {
    uint32_t storedChecksum = calculateChecksum(storedConfig);
    if (storedChecksum == storedConfig.checksum) {
      EEPROM.put(BACKUP_SLOT_ADDR, storedConfig);
    }
  }

  // STEP 2: Set write-in-progress flag
  uint32_t writeFlag = WRITE_FLAG_VALUE;
  EEPROM.put(WRITE_FLAG_ADDR, writeFlag);

  // STEP 3: Write new config to primary slot
  EEPROM.put(0, config);

  // STEP 4: Commit to flash
  if (!EEPROM.commit()) {
    Serial.println("[Storage] EEPROM commit failed!");
    // Clear write flag even on failure to prevent boot restore loop
    uint32_t clearFlag = WRITE_FLAG_CLEAR;
    EEPROM.put(WRITE_FLAG_ADDR, clearFlag);
    EEPROM.commit(); // Best effort
    return;
  }

  // STEP 5: Verify write succeeded
  ConfigData verifyConfig;
  EEPROM.get(0, verifyConfig);
  uint32_t verifyChecksum = calculateChecksum(verifyConfig);

  if (verifyChecksum != verifyConfig.checksum || verifyConfig.magic != CONFIG_MAGIC) {
    Serial.println("[Storage] Write verification FAILED! Attempting recovery...");
    // Restore from backup
    ConfigData backupConfig;
    EEPROM.get(BACKUP_SLOT_ADDR, backupConfig);
    EEPROM.put(0, backupConfig);
    EEPROM.commit();
    // Clear write flag after recovery
    uint32_t clearFlag = WRITE_FLAG_CLEAR;
    EEPROM.put(WRITE_FLAG_ADDR, clearFlag);
    EEPROM.commit();
    Serial.println("[Storage] Recovery attempted and write flag cleared");
    return;
  }

  // STEP 6: Clear write-in-progress flag
  uint32_t clearFlag = WRITE_FLAG_CLEAR;
  EEPROM.put(WRITE_FLAG_ADDR, clearFlag);
  EEPROM.commit();

  _lastSaveTime = now;
  _lastWrittenChecksum = newChecksum;
  _pendingSave = false;
  Serial.println("[Storage] Config saved (atomic, verified)");
}

void StorageManager::flushPending() {
  if (_pendingSave) {
    unsigned long now = millis();
    if (now - _lastSaveTime >= MIN_SAVE_INTERVAL_MS) {
      _pendingSave = false;  // Clear before save to prevent recursion
      save();
    }
  }
}

void StorageManager::reset() {
  memset(&config, 0, sizeof(ConfigData));
  config.magic = CONFIG_MAGIC;
  config.version = CONFIG_VERSION;
  config.setup_complete = false;

  config.stepper_top_position = 0;
  config.stepper_bottom_position = 0; // Uncalibrated
  config.stepper_current_position = 0;
  config.stepper_open_speed = 2000;
  config.stepper_close_speed = 2000;
  config.stepper_relax_steps = 128;
  config.stepper_stop_delay_ms = 3000;
  config.stepper_acceleration = 2000;

  // V8 Defaults
  config.gmt_offset = 0;
  config.daylight_offset = 0;
  config.morning_days = 0x7F; // 1111111 (All days)
  config.night_days = 0x7F;   // 1111111 (All days)
  for (int i = 0; i < 7; i++) {
    config.morning_time_h[i] = 7;     // 07:00 default
    config.morning_time_m[i] = 0;
    config.morning_duration[i] = 30;  // 30 minutes
    config.morning_target[i] = 100;   // Fully open
  }

  // Automation config defaults — prevent dangerous zero-values from memset.
  // Without these, a fresh/reset device would have:
  //   motion_timeout=0 → presence auto-close fires instantly
  //   temp_threshold=0 → any temperature triggers heat protection
  //   night_time=00:00 → midnight instead of a reasonable bedtime
  //   presence_open_target=0 → "open when entered" opens to closed position
  config.sunset_offset = 0;           // No offset from actual sunset
  config.sunset_target = 0;           // Fully closed at sunset
  config.motion_timeout = 300;        // 5 minutes (in seconds)
  config.presence_target = 0;         // Close fully when room empties
  config.presence_open_target = 100;  // Open fully when room entered
  config.presence_action = 0;         // close_only (safest default)
  config.presence_time_filter = 0;    // all (no time filtering)
  config.night_time_h = 22;           // 22:00 (reasonable bedtime)
  config.night_time_m = 0;
  config.night_target = 0;            // Fully closed at night
  config.temp_threshold = 35;         // 35°C (reasonable heat trigger)
  config.temp_target = 20;            // Close to 20% for shade

  // Pre-populate MQTT config from secrets.h defaults
  config.mqtt_enabled = (strlen(SECRET_MQTT_BROKER) > 0);
  strlcpy(config.mqtt_broker, SECRET_MQTT_BROKER, sizeof(config.mqtt_broker));
  config.mqtt_port = SECRET_MQTT_PORT;
  strlcpy(config.mqtt_user, SECRET_MQTT_USER, sizeof(config.mqtt_user));
  strlcpy(config.mqtt_pass, SECRET_MQTT_PASS, sizeof(config.mqtt_pass));
  config.twt_enabled = false;

  // Create device ID from raw MAC bytes — guaranteed unique per IEEE
  uint8_t macAddr[6];
  esp_read_mac(macAddr, ESP_MAC_WIFI_STA);
  snprintf(config.mqtt_device_id, sizeof(config.mqtt_device_id), "%02X%02X%02X",
           macAddr[3], macAddr[4], macAddr[5]);

  // CRITICAL: Force immediate write — bypass the 2-second save throttle.
  // A reset is a critical state change that must be persisted immediately,
  // especially since it is often followed by a device reboot.
  _lastSaveTime = 0;
  save();
}

bool StorageManager::isSetupComplete() const { return config.setup_complete; }

void StorageManager::markSetupComplete() {
  config.setup_complete = true;
  // CRITICAL: Force immediate write — bypass the 2-second save throttle.
  // This is called right before ESP.restart(). If save() is throttled
  // (because WiFi credentials were just saved moments ago), setup_complete
  // never persists to flash, and the device loops back into setup mode
  // on every boot.
  _lastSaveTime = 0;
  save();
}

uint32_t StorageManager::calculateChecksum(const ConfigData &data) {
  // CRC32 (IEEE 802.3 polynomial) for robust corruption detection.
  // Replaces the old simple byte-sum which had poor collision resistance.
  uint32_t crc = 0xFFFFFFFF;
  const uint8_t *ptr = (const uint8_t *)&data;
  for (size_t i = 0; i < sizeof(ConfigData); i++) {
    // Skip the checksum field bytes to avoid circular dependency
    if (i >= offsetof(ConfigData, checksum) &&
        i < offsetof(ConfigData, checksum) + sizeof(data.checksum)) {
      continue;
    }
    uint8_t b = ptr[i];
    crc ^= b;
    for (int j = 0; j < 8; j++) {
      if (crc & 1)
        crc = (crc >> 1) ^ 0xEDB88320;
      else
        crc >>= 1;
    }
  }
  return crc ^ 0xFFFFFFFF;
}
