#include <zephyr/fs/fs.h>
#include <zephyr/fs/littlefs.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/logging/log.h>
#include <fs_mgmt.h>

LOG_MODULE_REGISTER(fs_mgmt, CONFIG_FS_MGMT_LOG_LEVEL);
#define STORAGE_PARTITION_ID FIXED_PARTITION_ID(littlefs_storage)
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(fs_storage_data);

static struct fs_mount_t fs_storage_mnt = {
    .type = FS_LITTLEFS,
    .fs_data = &fs_storage_data,
    .storage_dev = (void *)STORAGE_PARTITION_ID,
    .mnt_point = CONFIG_FS_MGMT_MOUNT_POINT,
};

#ifdef CONFIG_FS_MGMT_MCUMGR_HANDLER
#include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
#include <zephyr/mgmt/mcumgr/smp/smp.h>
#include <zephyr/fs/fs.h>
#include <zcbor_decode.h>
#include <zcbor_encode.h>
#include <mgmt/mcumgr/util/zcbor_bulk.h>

#define CUSTOM_GROUP_ID 64
#define CMD_LS 0
#define CMD_RM 1

static int custom_ls_handler(struct smp_streamer *ctxt)
{
    struct fs_dir_t dirp;
    struct fs_dirent entry;
    int file_count = 0;
    char path[64] = ""; // Startet leer

    zcbor_state_t *zsd = ctxt->reader->zs;
    zcbor_state_t *zse = ctxt->writer->zs;

    /* --- DECODING --- */
    struct zcbor_string res_path = {0};
    bool path_found = false;

    if (zcbor_map_start_decode(zsd))
    {
        while (zcbor_tstr_decode(zsd, &res_path))
        {
            if (res_path.len == 4 && memcmp(res_path.value, "path", 4) == 0)
            {
                struct zcbor_string path_val;
                if (zcbor_tstr_decode(zsd, &path_val))
                {
                    int len = MIN(path_val.len, sizeof(path) - 1);
                    memcpy(path, path_val.value, len);
                    path[len] = '\0';
                    path_found = true;
                }
            }
            else
            {
                zcbor_any_skip(zsd, NULL);
            }
        }
        zcbor_map_end_decode(zsd);
    }

    // Falls kein Pfad gesendet wurde, fange im Root an
    if (!path_found || strlen(path) == 0)
    {
        strcpy(path, "/");
    }

    /* --- PROCESSING & ENCODING --- */
    fs_dir_t_init(&dirp);
    if (fs_opendir(&dirp, path) != 0)
        return MGMT_ERR_ENOENT;

    // Zählen...
    while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0')
    {
        file_count++;
    }
    fs_closedir(&dirp);

    bool ok = zcbor_tstr_put_lit(zse, "files") && zcbor_list_start_encode(zse, file_count);

    if (fs_opendir(&dirp, path) == 0)
    {
        while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0')
        {
            const char *type_char = (entry.type == FS_DIR_ENTRY_DIR) ? "d" : "f";
            ok = ok && zcbor_map_start_encode(zse, 2) &&
                 zcbor_tstr_put_lit(zse, "n") &&
                 zcbor_tstr_encode(zse, &(struct zcbor_string){.value = (const uint8_t *)entry.name, .len = strlen(entry.name)}) &&
                 zcbor_tstr_put_lit(zse, "t") &&
                 zcbor_tstr_encode(zse, &(struct zcbor_string){.value = (const uint8_t *)type_char, .len = 1}) &&
                 zcbor_map_end_encode(zse, 2);
        }
        fs_closedir(&dirp);
    }
    ok = ok && zcbor_list_end_encode(zse, file_count);
    return ok ? 0 : MGMT_ERR_ENOMEM;
}

static int custom_rm_handler(struct smp_streamer *ctxt)
{
    char path[64] = {0};
    zcbor_state_t *zsd = ctxt->reader->zs;
    zcbor_state_t *zse = ctxt->writer->zs;
    bool path_found = false;
    struct zcbor_string key;

    /* --- DECODING --- */
    if (!zcbor_map_start_decode(zsd))
    {
        return MGMT_ERR_EINVAL;
    }

    while (zcbor_tstr_decode(zsd, &key))
    {
        if (key.len == 4 && memcmp(key.value, "path", 4) == 0)
        {
            struct zcbor_string val;
            if (zcbor_tstr_decode(zsd, &val))
            {
                size_t len = MIN(val.len, sizeof(path) - 1);
                memcpy(path, val.value, len);
                path[len] = '\0';
                path_found = true;
            }
        }
        else
        {
            zcbor_any_skip(zsd, NULL);
        }
    }
    zcbor_map_end_decode(zsd);

    if (!path_found)
    {
        return MGMT_ERR_EINVAL;
    }

    /* --- PROCESSING --- */
    int rc = fs_unlink(path);

    /* --- ENCODING RESPONSE --- */
    // Wir senden den Return-Code zurück (0 = Erfolg)
    bool ok = zcbor_tstr_put_lit(zse, "rc") && zcbor_int32_put(zse, rc);

    if (rc != 0)
    {
        LOG_WRN("Failed to remove %s: %d", path, rc);
        // Optional: Spezifische mgmt_err Mapping
        return (rc == -ENOENT) ? MGMT_ERR_ENOENT : MGMT_ERR_EUNKNOWN;
    }

    return ok ? 0 : MGMT_ERR_ENOMEM;
}

static const struct mgmt_handler custom_handlers[] = {
    [CMD_LS] = {
        .mh_read = custom_ls_handler,
        .mh_write = NULL},
    [CMD_RM] = {
        .mh_read = NULL,
        .mh_write = custom_rm_handler // Nutzung von WRITE für Löschvorgänge
    },
};

static struct mgmt_group custom_group = {
    .mg_handlers = custom_handlers,
    .mg_handlers_count = ARRAY_SIZE(custom_handlers),
    .mg_group_id = CUSTOM_GROUP_ID,
};

#endif /* CONFIG_FS_MGMT_MCUMGR_HANDLER */

int fs_mgmt_init(void)
{
    int rc;
    LOG_DBG("Initializing filesystem management module");
    rc = fs_mount(&fs_storage_mnt);
    if (rc)
    {
        LOG_ERR("Filesystem mount failed (err %d)", rc);
        return rc;
    }
    else
    {
        struct fs_statvfs stat;
        uint64_t total;
        uint64_t free;

        LOG_DBG("Filesystem mounted successfully at %s", fs_storage_mnt.mnt_point);

        rc = fs_statvfs(fs_storage_mnt.mnt_point, &stat);
        if (rc == 0)
        {
            total = (uint64_t)stat.f_blocks * (uint64_t)stat.f_frsize;
            free = (uint64_t)stat.f_bfree * (uint64_t)stat.f_frsize;
            uint64_t total_kb = total / 1024;
            uint64_t free_kb = free / 1024;
            uint64_t used_kb = total_kb - free_kb;
            uint64_t used_pct_x10 = 0;
            LOG_DBG("Filesystem total size/used/free size: %4llu/%4llu/%4llu KB",
                    (unsigned long long)total_kb,
                    (unsigned long long)used_kb,
                    (unsigned long long)free_kb);
            if (total_kb > 0)
            {
                used_pct_x10 = (used_kb * 1000) / total_kb;
            }
            if (used_pct_x10 >= 900)
            {
                LOG_ERR("Filesystem used: %llu.%llu%%",
                        (unsigned long long)(used_pct_x10 / 10),
                        (unsigned long long)(used_pct_x10 % 10));
            }
            else if (used_pct_x10 >= 750)
            {
                LOG_WRN("Filesystem used: %llu.%llu%%",
                        (unsigned long long)(used_pct_x10 / 10),
                        (unsigned long long)(used_pct_x10 % 10));
            }
            else
            {
                LOG_DBG("Filesystem used: %llu.%llu%%",
                        (unsigned long long)(used_pct_x10 / 10),
                        (unsigned long long)(used_pct_x10 % 10));
            }
        }
        else
        {
            LOG_WRN("Filesystem statvfs failed (err %d)", rc);
        }
    }

#ifdef CONFIG_FS_MGMT_MCUMGR_HANDLER
    mgmt_register_group(&custom_group);
    LOG_DBG("Custom MCUMGR group registered with ID %d", CUSTOM_GROUP_ID);
#endif /* CONFIG_FS_MGMT_MCUMGR_HANDLER */

    return 0;
}
