#include "storage.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_sntp.h"
#include "esp_vfs_fat.h"
#include "ff.h"
#include <lvgl.h>
#include <stdlib.h>

static const char *TAG = "storage";

static const char *base_path = "/storage";
static wl_handle_t wl_handle = WL_INVALID_HANDLE;

void fs_mount(void)
{
    const esp_vfs_fat_mount_config_t mount_config = {
        .format_if_mount_failed = false,
        .max_files = 4,
        .allocation_unit_size = CONFIG_WL_SECTOR_SIZE,
        .disk_status_check_enable = false,
        .use_one_fat = false,
    };

    ESP_LOGI(TAG, "Mounting FAT filesystem in read/write mode");
    esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(base_path, "storage", &mount_config, &wl_handle);

    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
    }
}

void fs_unmount(void)
{
    if (wl_handle != WL_INVALID_HANDLE)
    {
        ESP_LOGI(TAG, "Unmounting FAT filesystem");
        esp_err_t err = esp_vfs_fat_spiflash_unmount_rw_wl(base_path, wl_handle);
        if (err != ESP_OK)
        {
            ESP_LOGE(TAG, "Failed to unmount FATFS (%s)", esp_err_to_name(err));
        }
        else
        {
            ESP_LOGI(TAG, "FAT filesystem unmounted");
            wl_handle = WL_INVALID_HANDLE;
        }
    }
    else
    {
        ESP_LOGW(TAG, "FAT filesystem is not mounted");
    }
}

/**
 * Opens a file.
 * @param drv Pointer to the driver
 * @param path Path to the file
 * @param mode LV_FS_MODE_RD for reading, LV_FS_MODE_WR for writing
 * @return Pointer to a file object in case of success, otherwise NULL
 */
void *fs_open(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode)
{
    LV_UNUSED(drv);
    ESP_LOGI(TAG, "fs_open called for path: %s, mode: %d", path, mode);

    BYTE fatfs_mode;
    if (mode == LV_FS_MODE_WR)
    {
        fatfs_mode = FA_WRITE | FA_CREATE_ALWAYS;
    }
    else if (mode == LV_FS_MODE_RD)
    {
        fatfs_mode = FA_READ;
    }
    else
    {
        ESP_LOGE(TAG, "fs_open: Unknown mode: %d", mode);
        return NULL;
    }

    FIL *file_p = (FIL *)malloc(sizeof(FIL));
    if (file_p == NULL)
    {
        ESP_LOGE(TAG, "fs_open: Failed to allocate memory for file object");
        return NULL;
    }

    FRESULT res = f_open(file_p, path, fatfs_mode);
    if (res != FR_OK)
    {
        ESP_LOGE(TAG, "fs_open: f_open failed with error code: %d", res);
        free(file_p);
        return NULL;
    }

    ESP_LOGI(TAG, "fs_open: Successfully opened file: %s", path);
    return file_p;
}

/**
 * Closes an opened file.
 * @param drv Pointer to the driver
 * @param file_p Pointer to the file object
 * @return LV_FS_RES_OK on success, otherwise error code
 */
lv_fs_res_t fs_close(lv_fs_drv_t *drv, void *file_p)
{
    LV_UNUSED(drv);
    ESP_LOGI(TAG, "fs_close called.");

    if (file_p == NULL)
    {
        ESP_LOGE(TAG, "fs_close: file pointer is NULL.");
        return LV_FS_RES_INV_PARAM;
    }

    f_close((FIL *)file_p);
    free(file_p);
    return LV_FS_RES_OK;
}

/**
 * Reads data from a file.
 * @param drv Pointer to the driver
 * @param file_p Pointer to the file object
 * @param buf Buffer to read the data into
 * @param btr Number of bytes to read (Bytes to Read)
 * @param br Pointer to store the number of bytes actually read
 * @return LV_FS_RES_OK on success, otherwise error code
 */
lv_fs_res_t fs_read(lv_fs_drv_t *drv, void *file_p, void *buf, uint32_t btr, uint32_t *br)
{
    LV_UNUSED(drv);
    ESP_LOGI(TAG, "fs_read called, bytes to read: %" PRIu32, btr);

    FRESULT res = f_read((FIL *)file_p, buf, btr, (UINT *)br);
    if (res == FR_OK)
    {
        ESP_LOGI(TAG, "fs_read: Successfully read %" PRIu32 " bytes.", *br);
        return LV_FS_RES_OK;
    }
    else
    {
        ESP_LOGE(TAG, "fs_read: f_read failed with error code: %d", res);
        return LV_FS_RES_UNKNOWN;
    }
}

/**
 * Sets the read/write pointer in the file.
 * @param drv Pointer to the driver
 * @param file_p Pointer to the file object
 * @param pos Position to seek to
 * @param whence Starting point: LV_FS_SEEK_SET (beginning), LV_FS_SEEK_CUR (current), LV_FS_SEEK_END (end)
 * @return LV_FS_RES_OK on success, otherwise error code
 */
lv_fs_res_t fs_seek(lv_fs_drv_t *drv, void *file_p, uint32_t pos, lv_fs_whence_t whence)
{
    LV_UNUSED(drv);
    ESP_LOGI(TAG, "fs_seek: pos=%" PRIu32 ", whence=%d", pos, whence);

    // Map LVGL whence to FatFS f_lseek (f_lseek always starts from the beginning)
    switch (whence)
    {
    case LV_FS_SEEK_SET:
        f_lseek((FIL *)file_p, pos);
        break;
    case LV_FS_SEEK_CUR:
        f_lseek((FIL *)file_p, f_tell((FIL *)file_p) + pos);
        break;
    case LV_FS_SEEK_END: {
        FSIZE_t fsize = f_size((FIL *)file_p);
        ESP_LOGI(TAG, "fs_seek to end, file size: %lu", (unsigned long)fsize);
        f_lseek((FIL *)file_p, fsize + pos);
    }
    break;
    default:
        break;
    }
    return LV_FS_RES_OK;
}

/**
 * Returns the current position of the read/write pointer.
 * @param drv Pointer to the driver
 * @param file_p Pointer to the file object
 * @param pos_p Pointer to store the current position
 * @return LV_FS_RES_OK on success, otherwise error code
 */
lv_fs_res_t fs_tell(lv_fs_drv_t *drv, void *file_p, uint32_t *pos_p)
{
    LV_UNUSED(drv);
    ESP_LOGI(TAG, "fs_tell called.");

    *pos_p = f_tell((FIL *)file_p);
    ESP_LOGI(TAG, "fs_tell: Current position: %" PRIu32, *pos_p);
    return LV_FS_RES_OK;
}

/**************** */
void setup_fatfs()
{
    const esp_vfs_fat_mount_config_t mount_config = {
        .format_if_mount_failed = false,
        .max_files = 4,
        .allocation_unit_size = CONFIG_WL_SECTOR_SIZE,
        .disk_status_check_enable = false,
        .use_one_fat = false,
    };

    ESP_LOGI(TAG, "Mounting FAT filesystem in read/write mode");
    static wl_handle_t wl_handle = WL_INVALID_HANDLE;
    esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(base_path, "storage", &mount_config, &wl_handle);

    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
        return;
    }

    FILE *f;
    ESP_LOGI(TAG, "Reading file");

    const char *host_filename1 = "/storage/response.png";

    struct stat info;
    struct tm timeinfo;
    char buffer[32];

    if (stat(host_filename1, &info) < 0)
    {
        ESP_LOGE(TAG, "Failed to read file stats");
        return;
    }
    localtime_r(&info.st_mtime, &timeinfo);
    strftime(buffer, sizeof(buffer), "%Y-%m-%d", &timeinfo);

    ESP_LOGI(TAG, "The file '%s' was modified at date: %s", host_filename1, buffer);

    f = fopen(host_filename1, "rb");
    if (f == NULL)
    {
        ESP_LOGE(TAG, "Failed to open file for reading");
        return;
    }
    fclose(f);

    ESP_LOGI(TAG, "Unmounting FAT filesystem");
    ESP_ERROR_CHECK(esp_vfs_fat_spiflash_unmount_rw_wl(base_path, wl_handle));
    ESP_LOGI(TAG, "Done");
}