/**
 * SD Card Manager Implementation
 * Handles FAT32 SD card mounting, file operations, and directory management
 * Provides interface for web server file serving and log management
 */
#include "sd_manager.h"
#include <esp_log.h>
#include <esp_vfs_fat.h>
#include <sdmmc_cmd.h>
#include <driver/sdmmc_host.h>
#include <sys/stat.h>
#include <sys/unistd.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
// Note: statvfs is not available in ESP-IDF; space info currently unsupported
#include <esp_check.h>
#include <stdbool.h>
#include <esp_err.h>

static const char *TAG = "SD_MANAGER";

static bool s_mounted = false;
static sdmmc_card_t *s_card = NULL;

/**
 * Internal helper to create a directory if it does not exist.
 * Creates directory with 0775 permissions if it doesn't exist.
 * @param path Directory path to create
 * @return ESP_OK on success, ESP_FAIL on error
 */
static esp_err_t create_dir_if_needed(const char *path)
{
    struct stat st;
    if (stat(path, &st) == 0) {
        if (S_ISDIR(st.st_mode)) {
            return ESP_OK; // already exists
        }
        ESP_LOGE(TAG, "%s exists but is not a directory", path);
        return ESP_FAIL;
    }
    if (mkdir(path, 0775) != 0) {
        ESP_LOGE(TAG, "mkdir failed for %s: errno %d", path, errno);
        return ESP_FAIL;
    }
    return ESP_OK;
}

/**
 * Initialize SD card and mount FAT32 file system
 * Mounts SD card at /sdcard and creates required directory structure
 * @return ESP_OK on success, error code otherwise
 */
esp_err_t sd_manager_init(void)
{
    if (s_mounted) {
        return ESP_OK; // already mounted
    }

    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
        .format_if_mount_failed = SD_FORMAT_IF_MOUNT_FAIL,
        .max_files = SD_MAX_FILES,
        .allocation_unit_size = SD_ALLOC_UNIT_SIZE
    };

    // Use SDMMC host by default; users can change to SDSPI if wiring requires.
    sdmmc_host_t host = SDMMC_HOST_DEFAULT();
    sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
    slot_config.width = 1; // 1-bit mode for easier wiring

    esp_err_t ret = esp_vfs_fat_sdmmc_mount(SD_MOUNT_POINT, &host, &slot_config, &mount_config, &s_card);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to mount SD card: %s", esp_err_to_name(ret));
        return ret;
    }

    ESP_LOGI(TAG, "SD card mounted at %s", SD_MOUNT_POINT);
    s_mounted = true;

    // Ensure directory structure exists
    return sd_manager_create_directories();
}

esp_err_t sd_manager_deinit(void)
{
    if (!s_mounted) {
        return ESP_OK;
    }
    esp_vfs_fat_sdcard_unmount(SD_MOUNT_POINT, s_card);
    s_card = NULL;
    s_mounted = false;
    ESP_LOGI(TAG, "SD card unmounted");
    return ESP_OK;
}

bool sd_manager_is_available(void)
{
    return s_mounted;
}

/**
 * Create required directory structure on SD card
 * Creates /sdcard/www, /sdcard/logs, /sdcard/config, /sdcard/scripts
 * @return ESP_OK on success
 */
esp_err_t sd_manager_create_directories(void)
{
    ESP_RETURN_ON_FALSE(s_mounted, ESP_ERR_INVALID_STATE, TAG, "SD card not mounted");

    ESP_ERROR_CHECK_WITHOUT_ABORT(create_dir_if_needed(SD_WWW_DIR));
    ESP_ERROR_CHECK_WITHOUT_ABORT(create_dir_if_needed(SD_LOGS_DIR));
    ESP_ERROR_CHECK_WITHOUT_ABORT(create_dir_if_needed(SD_CONFIG_DIR));
    ESP_ERROR_CHECK_WITHOUT_ABORT(create_dir_if_needed(SD_SCRIPTS_DIR));

    return ESP_OK;
}

/**
 * Write data to file on SD card
 * @param filepath Full path to file
 * @param data Data to write
 * @param size Size of data in bytes
 * @param append Whether to append or overwrite file
 * @return ESP_OK on success, ESP_FAIL on error
 */
esp_err_t sd_manager_write_file(const char *filepath, const void *data, size_t size, bool append)
{
    ESP_RETURN_ON_FALSE(filepath && data && size > 0, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");

    const char *mode = append ? "ab" : "wb";
    FILE *f = fopen(filepath, mode);
    if (!f) {
        ESP_LOGE(TAG, "Failed to open %s for write: errno %d", filepath, errno);
        return ESP_FAIL;
    }
    size_t written = fwrite(data, 1, size, f);
    fclose(f);
    return (written == size) ? ESP_OK : ESP_FAIL;
}

/**
 * Read data from file on SD card
 * @param filepath Full path to file
 * @param buffer Buffer to read into
 * @param size Size of buffer
 * @param bytes_read Pointer to store actual bytes read
 * @return ESP_OK on success, ESP_FAIL on error
 */
esp_err_t sd_manager_read_file(const char *filepath, void *buffer, size_t size, size_t *bytes_read)
{
    ESP_RETURN_ON_FALSE(filepath && buffer && size > 0, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");

    FILE *f = fopen(filepath, "rb");
    if (!f) {
        ESP_LOGE(TAG, "Failed to open %s for read: errno %d", filepath, errno);
        return ESP_FAIL;
    }
    size_t r = fread(buffer, 1, size, f);
    fclose(f);
    if (bytes_read) {
        *bytes_read = r;
    }
    return ESP_OK;
}

esp_err_t sd_manager_delete_file(const char *filepath)
{
    ESP_RETURN_ON_FALSE(filepath, ESP_ERR_INVALID_ARG, TAG, "Invalid path");
    if (unlink(filepath) != 0) {
        ESP_LOGE(TAG, "Failed to delete %s: errno %d", filepath, errno);
        return ESP_FAIL;
    }
    return ESP_OK;
}

bool sd_manager_file_exists(const char *filepath)
{
    struct stat st;
    return (stat(filepath, &st) == 0);
}

esp_err_t sd_manager_get_file_size(const char *filepath, size_t *size)
{
    ESP_RETURN_ON_FALSE(filepath && size, ESP_ERR_INVALID_ARG, TAG, "Invalid args");
    struct stat st;
    if (stat(filepath, &st) != 0) {
        return ESP_FAIL;
    }
    *size = st.st_size;
    return ESP_OK;
}

esp_err_t sd_manager_list_files(const char *dir_path, char *file_list, size_t buffer_size)
{
    ESP_RETURN_ON_FALSE(dir_path && file_list && buffer_size > 2, ESP_ERR_INVALID_ARG, TAG, "Invalid args");

    DIR *dir = opendir(dir_path);
    if (!dir) {
        ESP_LOGE(TAG, "Failed to open dir %s: errno %d", dir_path, errno);
        return ESP_FAIL;
    }

    // Begin JSON array
    size_t used = snprintf(file_list, buffer_size, "[");
    struct dirent *ent;
    bool first = true;
    while ((ent = readdir(dir)) != NULL) {
        if (ent->d_type == DT_REG) {
            if (!first) {
                used += snprintf(file_list + used, buffer_size - used, ",");
            }
            used += snprintf(file_list + used, buffer_size - used, "\"%s\"", ent->d_name);
            if (used >= buffer_size) {
                break; // buffer full
            }
            first = false;
        }
    }
    closedir(dir);
    snprintf(file_list + used, buffer_size - used, "]");
    return ESP_OK;
}

esp_err_t sd_manager_create_log_file(const char *log_type, char *filepath, size_t filepath_size)
{
    ESP_RETURN_ON_FALSE(log_type && filepath && filepath_size > 0, ESP_ERR_INVALID_ARG, TAG, "Invalid args");

    time_t now = time(NULL);
    struct tm tm_info;
    localtime_r(&now, &tm_info);
    char timestamp[32];
    strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", &tm_info);

    snprintf(filepath, filepath_size, "%s/%s%s_%s%s", SD_LOGS_DIR, LOG_FILE_PREFIX, log_type, timestamp, LOG_FILE_EXTENSION);

    // Create empty file
    FILE *f = fopen(filepath, "wb");
    if (!f) {
        ESP_LOGE(TAG, "Failed to create log file %s: errno %d", filepath, errno);
        return ESP_FAIL;
    }
    fclose(f);
    return ESP_OK;
}

esp_err_t sd_manager_write_log(const char *filepath, const char *level, const char *message)
{
    ESP_RETURN_ON_FALSE(filepath && level && message, ESP_ERR_INVALID_ARG, TAG, "Invalid args");
    time_t now = time(NULL);
    struct tm tm_info;
    localtime_r(&now, &tm_info);
    char timebuf[32];
    strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", &tm_info);

    char line[512];
    int len = snprintf(line, sizeof(line), "[%s] %s: %s\n", timebuf, level, message);
    return sd_manager_write_file(filepath, line, len, true);
}

esp_err_t sd_manager_get_card_info(sdmmc_card_t **info)
{
    ESP_RETURN_ON_FALSE(info, ESP_ERR_INVALID_ARG, TAG, "Invalid arg");
    *info = s_card;
    return s_mounted ? ESP_OK : ESP_ERR_INVALID_STATE;
}

esp_err_t sd_manager_get_space_info(uint64_t *free_bytes, uint64_t *total_bytes)
{
    ESP_RETURN_ON_FALSE(free_bytes && total_bytes, ESP_ERR_INVALID_ARG, TAG, "Invalid args");
    // Note: statvfs is not available in ESP-IDF; space info currently unsupported
    return ESP_ERR_NOT_SUPPORTED;
}

esp_err_t sd_manager_mkdir(const char *path)
{
    ESP_RETURN_ON_FALSE(s_mounted, ESP_ERR_INVALID_STATE, TAG, "SD card not mounted");
    ESP_RETURN_ON_FALSE(path, ESP_ERR_INVALID_ARG, TAG, "Invalid path");
    
    return create_dir_if_needed(path);
}

esp_err_t sd_manager_append_file(const char *filepath, const void *data, size_t size)
{
    ESP_RETURN_ON_FALSE(s_mounted, ESP_ERR_INVALID_STATE, TAG, "SD card not mounted");
    ESP_RETURN_ON_FALSE(filepath && data, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
    
    return sd_manager_write_file(filepath, data, size, true);
}

esp_err_t sd_manager_rename_file(const char *old_path, const char *new_path)
{
    ESP_RETURN_ON_FALSE(s_mounted, ESP_ERR_INVALID_STATE, TAG, "SD card not mounted");
    ESP_RETURN_ON_FALSE(old_path && new_path, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
    
    if (rename(old_path, new_path) != 0) {
        ESP_LOGE(TAG, "Failed to rename %s to %s: errno %d", old_path, new_path, errno);
        return ESP_FAIL;
    }
    
    return ESP_OK;
} 