#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <stdio.h>
#include <string.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/device.h>
#include <zephyr/fs/fcb.h>
#include <errno.h>

#include "fcb.h"

/*==============================================================================
 * GENERAL
 *============================================================================*/

LOG_MODULE_REGISTER(fcb, 3);

// Works only if settings are activated in prj.conf and hence storage_partition=settings_partitions is created
// #define FCB_PARTITION                       FIXED_PARTITION_ID(storage_partition)
// #define FCB_AREA                            FLASH_AREA_ID(storage_partition)

#define FCB_PARTITION                       FIXED_PARTITION_ID(test_partition)
#define FCB_AREA                            FLASH_AREA_ID(test_partition)

// #define FLASH_SECTOR_SIZE        		4096
// #define FCB_VERSION						1
// #define FCB_SECTOR_COUNT					16
// #define FCB_SCRATCH_SECTOR_COUNT			11

// This is for test to fill up quickly and see rotation working
#define FLASH_SECTOR_SIZE              4096
#define FCB_VERSION                    1
#define FCB_SECTOR_COUNT               4
#define FCB_SCRATCH_SECTOR_COUNT       2


// FCB sectors array, need to be inited per sector
static struct flash_sector fcb_instance_sectors[FCB_SECTOR_COUNT];

// Add missing fcb_instance definition
static struct fcb fcb_instance;

int FCB_init()
{
    // Log struct size for debug
    LOG_INF("sizeof(struct FCB_sensor_data) = %zu", sizeof(struct FCB_sensor_data));
    // BUILD_ASSERT(sizeof(struct FCB_sensor_data) == 84, "FCB_sensor_data struct size mismatch! Check packing and padding.");
	struct fcb *fcb = &fcb_instance;
	const struct flash_area *log_fa = NULL;
	const struct flash_parameters *flash_params = NULL;
	int rc;

	memset(fcb, 0, sizeof(*fcb));
	
	// Init the array of fcb_instance_sectors, need to init it per sector
	memset(fcb_instance_sectors, 0, sizeof(fcb_instance_sectors));
	for (int i = 0; i < FCB_SECTOR_COUNT; i++) {
		fcb_instance_sectors[i].fs_off = i * FLASH_SECTOR_SIZE;
		fcb_instance_sectors[i].fs_size = FLASH_SECTOR_SIZE;
	}

	// 1. Open flash area
    rc = flash_area_open(FCB_AREA, &log_fa);
    if (rc) {
        LOG_ERR("Failed to open log partition: %d", rc);
        return -ENODEV;
    }

	flash_params = flash_get_parameters(log_fa->fa_dev);
	if (!flash_params) {
		LOG_ERR("Failed to get flash parameters\n");
		return -ENODEV;
	}

	//2. Must erase the fcb->f_sectors before fcb_init, otherwise the f_magic checking will be failed!
    LOG_INF("Erasing Flash area for FCB initialization with size %zu bytes", log_fa->fa_size);
    rc = flash_area_erase(log_fa, 0, log_fa->fa_size);
    if (rc != 0) {
        LOG_ERR("Failed to erase Flash area, error %d", rc);
        flash_area_close(log_fa);
        return rc;
    }

	// 3. int FCB instance
	fcb->f_magic = 0x1a1a1a1a;
    fcb->f_version = FCB_VERSION;
    fcb->f_sector_cnt = FCB_SECTOR_COUNT; 
	fcb->f_scratch_cnt = FCB_SCRATCH_SECTOR_COUNT;
    fcb->f_sectors = fcb_instance_sectors;
    fcb->f_align = flash_params->write_block_size;

	LOG_INF("FCB params: magic=0x%08x, version=%d, sector_cnt=%d, scratch_cnt=%d, sectors_ptr=%p, align=%u",
		fcb->f_magic, fcb->f_version, fcb->f_sector_cnt, fcb->f_scratch_cnt, (void*)fcb->f_sectors, fcb->f_align);

    rc = fcb_init(log_fa->fa_id, fcb);
    
    if (rc) {
        LOG_ERR("Init failed with error %d", rc);
        return rc;
    } else {
		LOG_INF("Init ok %d", rc);
	}

	flash_area_close(log_fa);

	return 0;
}

int FCB_append_entry(const struct FCB_sensor_data* data, size_t len)
{
    struct fcb_entry entry_descriptor;
    int rc;

    LOG_INF("FCB_append_entry called with len=%zu", len);

    memset(&entry_descriptor, 0, sizeof(struct fcb_entry));
    rc = fcb_append(&fcb_instance, len, &entry_descriptor);
    if (rc) {
        // Do rotation in case of reaching the end of partition
        if (rc == -ENOSPC) {
            LOG_ERR("No space left, rotating FCB and try again");
            rc = fcb_rotate(&fcb_instance);
            if (rc) {
                LOG_ERR("FCB rotate failed with error %d", rc);
                return rc;
            }
            rc = fcb_append(&fcb_instance, len, &entry_descriptor);
            if (rc) {
                LOG_ERR("Failed to append data after rotate: %d", rc);
                return rc;
            }
        } else {
            LOG_ERR("Failed to append data: %d", rc);
            return rc;
        }
    }

    // Check if writing to old sector or to new one
    static const struct flash_sector *last_sector = NULL;
    if (entry_descriptor.fe_sector != last_sector) {
        int sector_idx = -1;
        for (int i = 0; i < FCB_SECTOR_COUNT; i++) {
            if (entry_descriptor.fe_sector == &fcb_instance_sectors[i]) {
                sector_idx = i;
                break;
            }
        }
        LOG_WRN("FCB sector change: now writing to sector %d (address %p)", sector_idx, (void*)entry_descriptor.fe_sector);
        last_sector = entry_descriptor.fe_sector;
    }

    rc = flash_area_write(fcb_instance.fap, entry_descriptor.fe_data_off, data, len);
    if (rc) {
        LOG_ERR("Failed to write to FCB: %d", rc);
        fcb_append_finish(&fcb_instance, &entry_descriptor);
        return rc;
    }

    // Log where the entry is written
    int sector_idx = -1;
    for (int i = 0; i < FCB_SECTOR_COUNT; i++) {
        if (entry_descriptor.fe_sector == &fcb_instance_sectors[i]) {
            sector_idx = i;
            break;
        }
    }
    LOG_INF("[append] Wrote entry: fe_sector=%p, fe_elem_off=0x%08lx, fe_data_off=0x%08lx, fe_data_len=%u, sector=%d", 
        (void*)entry_descriptor.fe_sector, 
        (unsigned long)entry_descriptor.fe_elem_off, 
        (unsigned long)entry_descriptor.fe_data_off, 
        (unsigned int)entry_descriptor.fe_data_len, 
        sector_idx);

    rc = fcb_append_finish(&fcb_instance, &entry_descriptor);
    if (rc) {
        LOG_ERR("FCB append finish failed with error %d", rc);
        return rc;
    }
    
    FCB_estimate_statistics();

    return 0;
}

// void FCB_read_sector(uint16_t sector_no)
// {
// 	struct fcb_entry entry_desc, first_entry_desc;
// 	struct FCB_sensor_data data;
// 	int rc;

//     memset(&entry_desc, 0, sizeof(entry_desc));
//     entry_desc.fe_sector = &fcb_instance_sectors[sector_no];
//     // Start with the first entry in this sector
//     rc = fcb_getnext(&fcb_instance, &entry_desc);
//     if (rc != 0) {
//         // No valid entry in this sector
//         LOG_INF("No valid entries in sector %u", sector_no);
//         return;
//     }
//     memcpy(&first_entry_desc, &entry_desc, sizeof(struct fcb_entry));

//     while(rc == 0) {
//         if(entry_desc.fe_sector == first_entry_desc.fe_sector){
//             rc = flash_area_read(fcb_instance.fap, entry_desc.fe_data_off, &data, entry_desc.fe_data_len);
//             if (rc) {
//                 LOG_ERR("Failed to read entry with error %d", rc);
//                 // Try next entry
//                 rc = fcb_getnext(&fcb_instance, &entry_desc);
//                 continue;
//             } else{
//                 LOG_INF("sensor_ts: %llu, message_counter: %u, temp_deg_c: %d, humidity_percent: %u, pressure_kpa: %u",
//                     data.payload.sensor_ts,
//                     data.payload.message_counter,
//                     data.payload.temp_deg_c,
//                     data.payload.humidity_percent,
//                     data.payload.pressure_kpa
//                 );
//             }

//             // Get the next entry
//             rc = fcb_getnext(&fcb_instance, &entry_desc);
//         } else {
//             break;
//         }
//     }
// }

// void FCB_read_all_sectors(void)
// {
//     for (uint16_t i = 0; i < FCB_SECTOR_COUNT; i++) {
//         LOG_INF("Reading sector %u", i);
//         FCB_read_sector(i);
//     }
// }

static int fcb_sector_walk_cb(struct fcb_entry_ctx *entry_ctx, void *arg)
{
    uint16_t sector_no = *(uint16_t *)arg;
    struct FCB_sensor_data data;
    const struct flash_sector *sector = entry_ctx->loc.fe_sector;
    // Find sector index
    int idx = -1;
    for (int i = 0; i < FCB_SECTOR_COUNT; i++) {
        if (sector == &fcb_instance_sectors[i]) {
            idx = i;
            break;
        }
    }
    if (idx == sector_no) {
        // Print entry offset and length
        LOG_INF("[walk] sector %u: entry offset=0x%08lx, len=%u", sector_no, (unsigned long)entry_ctx->loc.fe_data_off, entry_ctx->loc.fe_data_len);
        // Read raw bytes for debug
        uint8_t raw[sizeof(struct FCB_sensor_data)] = {0};
        int rc = flash_area_read(entry_ctx->fap, entry_ctx->loc.fe_data_off, raw, entry_ctx->loc.fe_data_len);
        if (rc) {
            LOG_ERR("Failed to read entry with error %d", rc);
        } else {
            // Print raw bytes
            // char hexbuf[3 * sizeof(raw) + 1];
            // int hexlen = 0;
            // for (int i = 0; i < entry_ctx->loc.fe_data_len && i < (int)sizeof(raw); i++) {
            //     hexlen += snprintf(&hexbuf[hexlen], sizeof(hexbuf) - hexlen, "%02X ", raw[i]);
            // }
            // hexbuf[hexlen] = '\0';
            // LOG_INF("[walk] sector %u: raw: %s", sector_no, hexbuf);
            // Also decode as struct if possible
            if (entry_ctx->loc.fe_data_len == sizeof(struct FCB_sensor_data)) {
                memcpy(&data, raw, sizeof(struct FCB_sensor_data));
                LOG_INF("[walk] sector %u: sensor_ts: %llu, message_counter: %u, temp_deg_c: %d, humidity_percent: %u, pressure_kpa: %u",
                    sector_no,
                    data.payload.sensor_ts,
                    data.payload.message_counter,
                    data.payload.temp_deg_c,
                    data.payload.humidity_percent,
                    data.payload.pressure_kpa
                );
            }
            k_sleep(K_MSEC(100)); // Small delay for log readability
        }
    }
    return 0;
}

void FCB_read_all_sectors(void)
{
    for (uint16_t i = 0; i < FCB_SECTOR_COUNT; i++) {
        LOG_INF("[walk] Reading sector %u", i);
        fcb_walk(&fcb_instance, NULL, fcb_sector_walk_cb, &i);
    }
}

void FCB_estimate_statistics(void)
{
    size_t used_bytes = 0;
    size_t total_bytes = 0;
    size_t free_bytes = 0;
    struct fcb_entry entry;
    int rc;

    // Calculate total space
    for (int i = 0; i < fcb_instance.f_sector_cnt; i++) {
        total_bytes += fcb_instance.f_sectors[i].fs_size;
    }

    // Calculate used space
    memset(&entry, 0, sizeof(entry));
    rc = fcb_getnext(&fcb_instance, &entry);
    while (rc == 0) {
        used_bytes += entry.fe_data_len;
        rc = fcb_getnext(&fcb_instance, &entry);
    }

    // Calculate free space
    free_bytes = (total_bytes > used_bytes) ? (total_bytes - used_bytes) : 0;

    LOG_INF("FCB statistics: used=%zu bytes, free=%zu bytes, total=%zu bytes (%.1f%% free)",
        used_bytes, free_bytes, total_bytes,
        (total_bytes > 0) ? (double)free_bytes / (double)total_bytes * 100.0 : 0.0);
}

void FCB_readback(void)
{
    const struct flash_area *log_fa = NULL;
    int rc = flash_area_open(FCB_AREA, &log_fa);
    if (rc) {
        LOG_ERR("Failed to open log partition for readback: %d", rc);
        return;
    }
    for (int sector = 0; sector < 2; sector++) {
        off_t sector_off = fcb_instance_sectors[sector].fs_off;
        size_t entry_size = 84;
        for (int entry = 0; entry < 3; entry++) {
            off_t entry_off = sector_off + 0x0c + entry * entry_size;
            uint8_t entry_buf[84];
            rc = flash_area_read(log_fa, entry_off, entry_buf, entry_size);
            if (rc != 0) {
                LOG_ERR("Failed to read sector %d entry %d at offset 0x%08lx, error %d", sector, entry, (unsigned long)entry_off, rc);
            } else {
                char hexbuf[3 * entry_size + 1];
                int hexlen = 0;
                for (size_t i = 0; i < entry_size; i++) {
                    hexlen += snprintf(&hexbuf[hexlen], sizeof(hexbuf) - hexlen, "%02X ", entry_buf[i]);
                }
                hexbuf[hexlen] = '\0';
                LOG_INF("Sector %d entry %d at offset 0x%08lx: %s", sector, entry, (unsigned long)entry_off, hexbuf);
            }
        }
    }
    flash_area_close(log_fa);
}