#include "factory_reset.h"

#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"

#include <cinttypes>

#if !defined(USE_RP2040) && !defined(USE_HOST)

namespace esphome::factory_reset {

static const char *const TAG = "factory_reset";
static const uint32_t POWER_CYCLES_KEY = 0xFA5C0DE;

static bool was_power_cycled() {
#ifdef USE_ESP32
  return esp_reset_reason() == ESP_RST_POWERON;
#endif
#ifdef USE_ESP8266
  auto reset_reason = EspClass::getResetReason();
  return ESPHOME_strcasecmp_P(reset_reason.c_str(), ESPHOME_PSTR("power On")) == 0 ||
         ESPHOME_strcasecmp_P(reset_reason.c_str(), ESPHOME_PSTR("external system")) == 0;
#endif
#ifdef USE_LIBRETINY
  auto reason = lt_get_reboot_reason();
  return reason == REBOOT_REASON_POWER || reason == REBOOT_REASON_HARDWARE;
#endif
}

void FactoryResetComponent::dump_config() {
  uint8_t count = 0;
  this->flash_.load(&count);
  ESP_LOGCONFIG(TAG,
                "Factory Reset by Reset:\n"
                "  Max interval between resets: %u seconds\n"
                "  Current count: %u\n"
                "  Factory reset after %u resets",
                this->max_interval_, count, this->required_count_);
}

void FactoryResetComponent::save_(uint8_t count) {
  this->flash_.save(&count);
  global_preferences->sync();
  this->defer([count, this] { this->increment_callback_.call(count, this->required_count_); });
}

void FactoryResetComponent::setup() {
  this->flash_ = global_preferences->make_preference<uint8_t>(POWER_CYCLES_KEY, true);
  if (was_power_cycled()) {
    uint8_t count = 0;
    this->flash_.load(&count);
    // this is a power on reset or external system reset
    count++;
    if (count == this->required_count_) {
      ESP_LOGW(TAG, "Reset count reached, factory resetting");
      global_preferences->reset();
      // delay to allow log to be sent
      delay(100);         // NOLINT
      App.safe_reboot();  // should not return
    }
    this->save_(count);
    ESP_LOGD(TAG, "Power on reset detected, incremented count to %u", count);
    this->set_timeout(static_cast<uint32_t>(this->max_interval_) * 1000, [this]() {
      ESP_LOGD(TAG, "No reset in the last %u seconds, resetting count", this->max_interval_);
      this->save_(0);  // reset count
    });
  } else {
    this->save_(0);  // reset count if not a power cycle
  }
}

}  // namespace esphome::factory_reset

#endif  // !defined(USE_RP2040) && !defined(USE_HOST)
