#include "StartupSdManager.h"

#include <stdarg.h>
#include "driver/gpio.h"

StartupSdManager::StartupSdManager(Print& serial) : serial_(serial) {}

bool StartupSdManager::begin(const SdWatcherConfig& cfg, SdStatusCallback callback) {
  cfg_ = cfg;
  callback_ = callback;

  forceSpiDeselected();
  dumpSdPins("very-early");

  if (!initPmuForSdPower()) {
    return false;
  }

  cycleSdRail();
  delay(cfg_.startupWarmupMs);

  bool warmMounted = false;
  for (uint8_t i = 0; i < 3; ++i) {
    if (mountPreferred(false)) {
      warmMounted = true;
      break;
    }
    delay(200);
  }

  if (!warmMounted) {
    logf("Watcher: startup preferred mount failed, retrying with extended settle");
    cycleSdRail(400, 1200);
    delay(cfg_.startupWarmupMs + 1500);
    warmMounted = mountCardFullScan();
  }

  if (warmMounted) {
    setStateMounted();
  } else {
    setStateAbsent();
  }
  return true;
}

void StartupSdManager::update() {
  const uint32_t now = millis();
  const uint32_t pollInterval =
      (watchState_ == SdWatchState::MOUNTED) ? cfg_.pollIntervalMountedMs : cfg_.pollIntervalAbsentMs;

  if ((uint32_t)(now - lastPollMs_) < pollInterval) {
    return;
  }
  lastPollMs_ = now;

  if (watchState_ == SdWatchState::MOUNTED) {
    if (verifyMountedCard()) {
      presentVotes_ = 0;
      absentVotes_ = 0;
      return;
    }

    if (mountPreferred(false) && verifyMountedCard()) {
      presentVotes_ = 0;
      absentVotes_ = 0;
      return;
    }

    absentVotes_++;
    presentVotes_ = 0;
    if (absentVotes_ >= cfg_.votesToAbsent) {
      setStateAbsent();
      absentVotes_ = 0;
    }
    return;
  }

  bool mounted = mountPreferred(false);
  if (!mounted && (uint32_t)(now - lastFullScanMs_) >= cfg_.fullScanIntervalMs) {
    lastFullScanMs_ = now;
    if (cfg_.recoveryRailCycleOnFullScan) {
      logf("Watcher: recovery rail cycle before full scan");
      cycleSdRail(cfg_.recoveryRailOffMs, cfg_.recoveryRailOnSettleMs);
      delay(150);
    }
    logf("Watcher: preferred probe failed, running full scan");
    mounted = mountCardFullScan();
  }

  if (mounted) {
    presentVotes_++;
    absentVotes_ = 0;
    if (presentVotes_ >= cfg_.votesToPresent) {
      setStateMounted();
      presentVotes_ = 0;
    }
  } else {
    absentVotes_++;
    presentVotes_ = 0;
    if (absentVotes_ >= cfg_.votesToAbsent) {
      setStateAbsent();
      absentVotes_ = 0;
    }
  }
}

bool StartupSdManager::consumeMountedEvent() {
  bool out = mountedEventPending_;
  mountedEventPending_ = false;
  return out;
}

bool StartupSdManager::consumeRemovedEvent() {
  bool out = removedEventPending_;
  removedEventPending_ = false;
  return out;
}

bool StartupSdManager::forceRemount() {
  logf("Watcher: manual rescan requested");
  presentVotes_ = 0;
  absentVotes_ = 0;
  lastPollMs_ = 0;
  lastFullScanMs_ = millis();

  cycleSdRail(cfg_.recoveryRailOffMs, cfg_.recoveryRailOnSettleMs);
  delay(cfg_.startupWarmupMs);

  if (mountCardFullScan()) {
    setStateMounted();
    return true;
  }

  setStateAbsent();
  return false;
}

void StartupSdManager::logf(const char* fmt, ...) {
  char msg[196];
  va_list args;
  va_start(args, fmt);
  vsnprintf(msg, sizeof(msg), fmt, args);
  va_end(args);
  serial_.printf("[%10lu][%06lu] %s\r\n",
                 (unsigned long)millis(),
                 (unsigned long)logSeq_++,
                 msg);
}

void StartupSdManager::notify(SdEvent event, const char* message) {
  if (callback_ != nullptr) {
    callback_(event, message);
  }
}

void StartupSdManager::forceSpiDeselected() {
  pinMode(tbeam_supreme::sdCs(), OUTPUT);
  digitalWrite(tbeam_supreme::sdCs(), HIGH);
  pinMode(tbeam_supreme::imuCs(), OUTPUT);
  digitalWrite(tbeam_supreme::imuCs(), HIGH);
}

void StartupSdManager::dumpSdPins(const char* tag) {
  if (!cfg_.enablePinDumps) {
    (void)tag;
    return;
  }

  const gpio_num_t cs = (gpio_num_t)tbeam_supreme::sdCs();
  const gpio_num_t sck = (gpio_num_t)tbeam_supreme::sdSck();
  const gpio_num_t miso = (gpio_num_t)tbeam_supreme::sdMiso();
  const gpio_num_t mosi = (gpio_num_t)tbeam_supreme::sdMosi();
  logf("PINS(%s): CS=%d SCK=%d MISO=%d MOSI=%d",
       tag, gpio_get_level(cs), gpio_get_level(sck), gpio_get_level(miso), gpio_get_level(mosi));
}

bool StartupSdManager::initPmuForSdPower() {
  if (!tbeam_supreme::initPmuForPeripherals(pmu_, &serial_)) {
    logf("ERROR: PMU init failed");
    return false;
  }
  return true;
}

void StartupSdManager::cycleSdRail(uint32_t offMs, uint32_t onSettleMs) {
  if (!cfg_.enableSdRailCycle) {
    return;
  }
  if (!pmu_) {
    logf("SD rail cycle skipped: pmu=null");
    return;
  }

  forceSpiDeselected();
  pmu_->disablePowerOutput(XPOWERS_BLDO1);
  delay(offMs);
  pmu_->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
  pmu_->enablePowerOutput(XPOWERS_BLDO1);
  delay(onSettleMs);
}

bool StartupSdManager::tryMountWithBus(SPIClass& bus, const char* busName, uint32_t hz, bool verbose) {
  SD.end();
  bus.end();
  delay(10);
  forceSpiDeselected();

  bus.begin(tbeam_supreme::sdSck(), tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi(), tbeam_supreme::sdCs());
  digitalWrite(tbeam_supreme::sdCs(), HIGH);
  delay(2);
  for (int i = 0; i < 10; i++) {
    bus.transfer(0xFF);
  }
  delay(2);

  if (verbose) {
    logf("SD: trying bus=%s freq=%lu Hz", busName, (unsigned long)hz);
  }

  if (!SD.begin(tbeam_supreme::sdCs(), bus, hz)) {
    if (verbose) {
      logf("SD: mount failed (possible non-FAT format, power, or bus issue)");
    }
    return false;
  }

  if (SD.cardType() == CARD_NONE) {
    SD.end();
    return false;
  }

  sdSpi_ = &bus;
  sdBusName_ = busName;
  sdFreq_ = hz;
  return true;
}

bool StartupSdManager::mountPreferred(bool verbose) {
  return tryMountWithBus(sdSpiH_, "HSPI", 400000, verbose);
}

bool StartupSdManager::mountCardFullScan() {
  const uint32_t freqs[] = {400000, 1000000, 4000000, 10000000};

  for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
    if (tryMountWithBus(sdSpiH_, "HSPI", freqs[i], true)) {
      logf("SD: card detected and mounted");
      return true;
    }
  }
  for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
    if (tryMountWithBus(sdSpiF_, "FSPI", freqs[i], true)) {
      logf("SD: card detected and mounted");
      return true;
    }
  }

  logf("SD: begin() failed on all bus/frequency attempts");
  return false;
}

bool StartupSdManager::verifyMountedCard() {
  File root = SD.open("/", FILE_READ);
  if (!root) {
    return false;
  }
  root.close();
  return true;
}

const char* StartupSdManager::cardTypeToString(uint8_t type) {
  switch (type) {
    case CARD_MMC: return "MMC";
    case CARD_SD: return "SDSC";
    case CARD_SDHC: return "SDHC/SDXC";
    default: return "UNKNOWN";
  }
}

void StartupSdManager::setStateMounted() {
  if (watchState_ != SdWatchState::MOUNTED) {
    mountedEventPending_ = true;
    removedEventPending_ = false;
    notify(SdEvent::CARD_MOUNTED, "mounted");
  }
  watchState_ = SdWatchState::MOUNTED;
}

void StartupSdManager::setStateAbsent() {
  if (watchState_ != SdWatchState::ABSENT) {
    removedEventPending_ = true;
    mountedEventPending_ = false;
    notify(SdEvent::CARD_REMOVED, "removed");
  }
  watchState_ = SdWatchState::ABSENT;
}

void StartupSdManager::printCardInfo() {
  if (!isMounted()) {
    logf("SD: no mounted card");
    return;
  }

  logf("SD: bus=%s freq=%lu type=%s sizeMB=%llu usedMB=%llu",
       sdBusName_,
       (unsigned long)sdFreq_,
       cardTypeToString(SD.cardType()),
       (unsigned long long)(SD.cardSize() / (1024ULL * 1024ULL)),
       (unsigned long long)(SD.usedBytes() / (1024ULL * 1024ULL)));
}

bool StartupSdManager::ensureDirRecursive(const char* path) {
  if (!path || path[0] != '/') {
    logf("DIR: invalid path");
    return false;
  }

  String full(path);
  int start = 1;
  while (start > 0 && start < (int)full.length()) {
    const int slash = full.indexOf('/', start);
    String partial = (slash < 0) ? full : full.substring(0, slash);
    if (!SD.exists(partial.c_str()) && !SD.mkdir(partial.c_str())) {
      logf("DIR: mkdir failed for %s", partial.c_str());
      return false;
    }
    if (slash < 0) {
      break;
    }
    start = slash + 1;
  }
  return true;
}

bool StartupSdManager::rewriteFile(const char* path, const char* payload) {
  if (!path || path[0] != '/') {
    return false;
  }

  String dir(path);
  const int slash = dir.lastIndexOf('/');
  if (slash > 0) {
    dir.remove(slash);
    if (!ensureDirRecursive(dir.c_str())) {
      return false;
    }
  }

  File file = SD.open(path, FILE_WRITE);
  if (!file) {
    return false;
  }
  file.print(payload ? payload : "");
  file.flush();
  file.close();
  return true;
}

void StartupSdManager::permissionsDemo(const char* path) {
  (void)path;
}
