// First, in its own block so the include sorter keeps it there: configuration.h supplies the
// variant defines mesh-pb-constants.h needs (portduino resolves MAX_NUM_NODES at runtime).
#include "configuration.h"

#include "MeshtasticOTA.h"
#ifdef ESP_PLATFORM
#include <Preferences.h>
#include <esp_ota_ops.h>
#endif

namespace MeshtasticOTA
{

static const char *nvsNamespace = "MeshtasticOTA";
static const char *combinedAppProjectName = "MeshtasticOTA";
static const char *bleOnlyAppProjectName = "MeshtasticOTA-BLE";
static const char *wifiOnlyAppProjectName = "MeshtasticOTA-WiFi";

static bool updated = false;

bool isUpdated()
{
    return updated;
}

void initialize()
{
    Preferences prefs;
    prefs.begin(nvsNamespace);
    if (prefs.getBool("updated")) {
        LOG_INFO("First boot after OTA update");
        updated = true;
        prefs.putBool("updated", false);
    }
    prefs.end();
}

void recoverConfig(meshtastic_Config_NetworkConfig *network)
{
    LOG_INFO("Recovering WiFi settings after OTA update");

    Preferences prefs;
    prefs.begin(nvsNamespace, true);
    String ssid = prefs.getString("ssid");
    String psk = prefs.getString("psk");
    prefs.end();

    network->wifi_enabled = true;
    strncpy(network->wifi_ssid, ssid.c_str(), sizeof(network->wifi_ssid));
    strncpy(network->wifi_psk, psk.c_str(), sizeof(network->wifi_psk));
}

void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash)
{
    LOG_INFO("Saving WiFi settings for upcoming OTA update");

    Preferences prefs;
    prefs.begin(nvsNamespace);
    prefs.putUChar("method", method);
    prefs.putBytes("ota_hash", ota_hash, 32);
    prefs.putString("ssid", network->wifi_ssid);
    prefs.putString("psk", network->wifi_psk);
    prefs.putBool("updated", false);
    prefs.end();
}

const esp_partition_t *getAppPartition()
{
    return esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL);
}

bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc)
{
    if (esp_ota_get_partition_description(part, app_desc) != ESP_OK) {
        LOG_INFO("esp_ota_get_partition_description failed");
        return false;
    }
    return true;
}

bool checkOTACapability(const esp_app_desc_t *app_desc, uint8_t method)
{
    // Combined loader supports all (both) transports, BLE and WiFi
    if (strcmp(app_desc->project_name, combinedAppProjectName) == 0) {
        LOG_INFO("OTA partition contains combined BLE/WiFi OTA Loader");
        return true;
    }
    if (method == METHOD_OTA_BLE && strcmp(app_desc->project_name, bleOnlyAppProjectName) == 0) {
        LOG_INFO("OTA partition contains BLE-only OTA Loader");
        return true;
    }
    if (method == METHOD_OTA_WIFI && strcmp(app_desc->project_name, wifiOnlyAppProjectName) == 0) {
        LOG_INFO("OTA partition contains WiFi-only OTA Loader");
        return true;
    }
    LOG_INFO("OTA partition does not contain a known OTA loader");
    return false;
}

bool trySwitchToOTA()
{
    const esp_partition_t *part = getAppPartition();

    if (part == NULL) {
        LOG_WARN("Can't get app partition in preparation of OTA reboot");
        return false;
    }

    uint8_t result = esp_ota_set_boot_partition(part);
    // Partition and app checks should now be done in the AdminModule before this is called
    if (result != ESP_OK) {
        LOG_WARN("Can't switch to OTA partition (reason %d)", result);
        return false;
    }

    return true;
}

const char *getVersion()
{
    const esp_partition_t *part = getAppPartition();
    static esp_app_desc_t app_desc;
    if (!getAppDesc(part, &app_desc))
        return "";
    return app_desc.version;
}

} // namespace MeshtasticOTA
