#include "fota_manager.h"

#include <stdio.h>

#include "esp_log.h"
#include "esp_system.h"
#include "esp_ota_ops.h"
#include "esp_partition.h"

static const char *TAG = "FOTA_MANAGER";

fota_err_t fota_manager_apply(const char *firmware_path)
{
    if (!firmware_path) {
        ESP_LOGE(TAG, "Firmware path is NULL");
        return FOTA_ERR_OPEN;
    }

    ESP_LOGI(TAG, "Starting FOTA from file: %s", firmware_path);

    FILE *fp = fopen(firmware_path, "rb");
    if (!fp) {
        ESP_LOGE(TAG, "Failed to open firmware file: %s", firmware_path);
        return FOTA_ERR_OPEN;
    }

    const esp_partition_t *running = esp_ota_get_running_partition();
    if (!running) {
        ESP_LOGE(TAG, "Failed to get running partition");
        fclose(fp);
        return FOTA_ERR_NO_UPDATE_PART;
    }

    ESP_LOGI(TAG, "Running partition: label=%s, addr=0x%lx, size=0x%lx",
             running->label, (unsigned long)running->address, (unsigned long)running->size);

    const esp_partition_t *update = esp_ota_get_next_update_partition(NULL);
    if (!update) {
        ESP_LOGE(TAG, "Failed to find update partition");
        fclose(fp);
        return FOTA_ERR_NO_UPDATE_PART;
    }

    ESP_LOGI(TAG, "Update partition: label=%s, addr=0x%lx, size=0x%lx",
             update->label, (unsigned long)update->address, (unsigned long)update->size);

    esp_ota_handle_t ota_handle = 0;
    esp_err_t err = esp_ota_begin(update, OTA_SIZE_UNKNOWN, &ota_handle);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err));
        fclose(fp);
        return FOTA_ERR_OTA_BEGIN;
    }

    uint8_t buffer[4096];
    size_t read_bytes = 0;
    size_t total_written = 0;

    while ((read_bytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
        err = esp_ota_write(ota_handle, buffer, read_bytes);
        if (err != ESP_OK) {
            ESP_LOGE(TAG, "esp_ota_write failed after %u bytes: %s",
                     (unsigned)total_written, esp_err_to_name(err));
            esp_ota_end(ota_handle);
            fclose(fp);
            return FOTA_ERR_OTA_WRITE;
        }
        total_written += read_bytes;
    }

    fclose(fp);

    ESP_LOGI(TAG, "FOTA written %u bytes to partition %s",
             (unsigned)total_written, update->label);

    err = esp_ota_end(ota_handle);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err));
        return FOTA_ERR_OTA_END;
    }

    err = esp_ota_set_boot_partition(update);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s", esp_err_to_name(err));
        return FOTA_ERR_SET_BOOT;
    }

    ESP_LOGI(TAG, "FOTA apply success. Next boot from: label=%s, addr=0x%lx",
             update->label, (unsigned long)update->address);

    return FOTA_OK;
}
