/*==============================================================================
 * fstorage.c - FDS (Flash Data Storage) configuration module
 *
 * Stores and loads device configuration to/from nRF52840 internal flash,
 * replacing external EEPROM. Coexists safely with the SoftDevice.
 *
 * Record management:
 *   CONFIG_FILE = 0x8010, CONFIG_REC_KEY = 0x7010 (single record)
 *
 * Magic number validation:
 *   Data loaded from flash is checked against CONFIG_MAGIC_NUMBER_VALUE.
 *   Mismatch -> reinitialise with factory defaults.
 *
 * FDS event post-processing:
 *   After a write/update completes, pending actions are executed:
 *   power-off (go_device_power_off), sleep (go_sleep_mode_enter),
 *   system reset (go_NVIC_SystemReset).
 *============================================================================*/

#include "sdk_config.h"

#include <string.h>

#include "app_error.h"
#include "boards.h"
#include "nrf_fstorage.h"
#include "nrf_soc.h"
#include "nrf_strerror.h"
#include "sdk_config.h"
#include "nrf_fstorage_sd.h"
#include "nrf_delay.h"
#include "ble_gap.h"
#include "fds.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#include "fstorage.h"
#include "nrf_pwr_mgmt.h"
#include "main.h"
#include "debug_print.h"


/* FDS record identifiers */
#define CONFIG_FILE     (0x8010)
#define CONFIG_REC_KEY  (0x7010)

/* Magic number used to validate stored data */
#define CONFIG_MAGIC_NUMBER_VALUE          (0x20260428)

/* Global configuration instance */
config_data_t m_config;

/* Post-processing flags (declared in main.c) */
extern bool go_device_power_off;
extern bool go_sleep_mode_enter;
extern bool go_NVIC_SystemReset;

/* FDS initialisation complete flag (set in fds_evt_handler) */
static bool volatile m_fds_initialized;

/* FDS write-in-progress flag */
bool fds_flag_write = false;

/* FDS record template pointing to m_config */
static fds_record_t const m_dummy_record =
{
    .file_id           = CONFIG_FILE,
    .key               = CONFIG_REC_KEY,
    .data.p_data       = (void const *)&m_config,
    /* The length of a record is always expressed in 4-byte units (words). */
    .data.length_words = (sizeof(m_config) + 3) / sizeof(uint32_t),
};


/* Default values */
int8_t  reset_status_dflt = 99;
uint8_t static_passkey_dflt[6] = DEFAULT_PASSKEY;

/*==============================================================================
 * fds_default_value_set - Initialise m_config with factory defaults
 *
 * Called when flash contains no valid configuration or the magic number
 * does not match.
 *============================================================================*/
void fds_default_value_set(void)
{
    /* HW number */
    memset(m_config.hw_no, 0, 12);
    memcpy(m_config.hw_no, HARDWARE_VERSION, strlen(HARDWARE_VERSION));

    /* Serial number */
    memset(m_config.serial_no, 0, 12);
    memcpy(m_config.serial_no, SERIAL_NUMBER, strlen(SERIAL_NUMBER));

    /* Static passkey */
    memcpy(m_config.static_passkey, static_passkey_dflt, 6);

    /* Bond delete — default: no pending delete */
    m_config.bond_data_delete = 0;

    /* Reset status */
    m_config.reset_status = reset_status_dflt;

    /* Device usage count */
    m_config.life_cycle  = 0;

    /* Piezo measurement parameter defaults */
    m_config.piezo_freq_option = 1;      /* 2.1 MHz */
    m_config.piezo_delay_us    = 10;     /* 10 us after burst */
    m_config.piezo_num_samples = 100;    /* 100 samples */
    m_config.piezo_cycles      = 3;      /* 7 cycles */
    m_config.piezo_averaging   = 3;      /* 3x averaging */

    /* Factory provisioning — default: not provisioned */
    m_config.factory_provisioned = 0;
}


/* Last FDS event ID (for debugging) */
static volatile uint8_t fds_last_evt = 0xFF;

/*==============================================================================
 * fds_evt_handler - FDS event callback
 *
 * FDS_EVT_INIT   : initialisation complete -> set m_fds_initialized
 * FDS_EVT_WRITE  : new record written       -> clear fds_flag_write
 * FDS_EVT_UPDATE : record updated            -> clear flag, then execute any
 *                  pending power-off / sleep / system reset
 *============================================================================*/
static void fds_evt_handler( fds_evt_t const *p_evt )
{
    fds_last_evt = p_evt->id;

    switch (p_evt->id)
    {
        case FDS_EVT_INIT:
            if (p_evt->result == NRF_SUCCESS)
            {
                m_fds_initialized = true;
            }
            break;

        case FDS_EVT_WRITE:
            fds_flag_write = false;
            break;

        case FDS_EVT_UPDATE:
            fds_flag_write = false;

            if (go_device_power_off == true)
            {
                device_power_off();
            }
            if (go_sleep_mode_enter == true)
            {
                sleep_mode_enter();
            }
            if (go_NVIC_SystemReset == true)
            {
                DBG_PRINTF("Off FDS_EVENT\r\n");
                NVIC_SystemReset();
            }
            break;

        case FDS_EVT_DEL_RECORD:
            break;

        case FDS_EVT_DEL_FILE:
            break;

        case FDS_EVT_GC:
            break;

        default:
            break;
    }
}


/*==============================================================================
 * wait_for_fds_ready - Block until FDS initialisation completes
 *
 * Times out after 3 seconds with an error log.
 *============================================================================*/
static void wait_for_fds_ready( void )
{
    uint32_t timeout = 0;
    while(!m_fds_initialized)
    {
        nrf_pwr_mgmt_run();
        nrf_delay_ms(1);
        timeout++;
        if (timeout > 3000)
        {
            DBG_PRINTF("[FDS] TIMEOUT!\r\n");
            break;
        }
    }
}


/*==============================================================================
 * config_load - Load configuration from FDS
 *
 * Flow:
 * 1. Search for CONFIG_FILE / CONFIG_REC_KEY (retry up to 10x, 100 ms apart)
 * 2. If found:
 *    - Open the record (on CRC error: delete and regenerate defaults)
 *    - Copy into m_config
 *    - Validate magic number; on mismatch: delete -> defaults -> rewrite
 * 3. If not found:
 *    - Write factory defaults as a new record, then reload
 *============================================================================*/
void config_load( void )
{
    ret_code_t rc;
    fds_record_desc_t desc = { 0 };
    fds_find_token_t  tok  = { 0 };
    uint8_t cfg_retry = 0;
    uint32_t fds_wait_cnt = 0;

 cfg_load_start:
    memset((char *)&desc, 0, sizeof(desc));
    memset((char *)&tok, 0, sizeof(tok));
    rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok);
    DBG_PRINTF("[FDS] find rc=%u\r\n", rc);

    /* FDS may not be fully ready yet - retry before writing defaults */
    if (rc != NRF_SUCCESS && cfg_retry < 10)
    {
        cfg_retry++;
        DBG_PRINTF("[FDS] retry %u/10\r\n", cfg_retry);
        nrf_delay_ms(100);
        goto cfg_load_start;
    }

    if (rc == NRF_SUCCESS)
    {
        fds_flash_record_t config = { 0 };

        rc = fds_record_open(&desc, &config);
        if (rc != NRF_SUCCESS)
        {
            /* CRC error or corrupt record - delete and use defaults */
            DBG_PRINTF("[FDS] open ERR=%u, deleting\r\n", rc);
            (void)fds_record_delete(&desc);
            fds_gc();
            fds_default_value_set();
            goto cfg_load_write_new;
        }

        memcpy(&m_config, config.p_data, sizeof(config_data_t));

        rc = fds_record_close(&desc);
        APP_ERROR_CHECK(rc);

        DBG_PRINTF("[FDS] magic=0x%08X (expect 0x%08X)\r\n", m_config.magic_number, CONFIG_MAGIC_NUMBER_VALUE);

        if (m_config.magic_number != (uint32_t)CONFIG_MAGIC_NUMBER_VALUE)
        {
            DBG_PRINTF("[FDS] FORMAT! overwriting with defaults\r\n");
            rc = fds_record_delete(&desc);
            APP_ERROR_CHECK(rc);
            m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;

            fds_default_value_set();

            rc = fds_record_update(&desc, &m_dummy_record);
            if ((rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH))
            {
                rc = fds_gc();
                APP_ERROR_CHECK(rc);
            }
            else
            {
                APP_ERROR_CHECK(rc);
            }
            goto cfg_load_start;
        }
        DBG_PRINTF("[FDS] Loaded OK\r\n");
    }
    else
    {
        cfg_load_write_new:
        DBG_PRINTF("[FDS] New - writing defaults\r\n");
        m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;

        fds_default_value_set();

        fds_flag_write = true;
        rc = fds_record_write(&desc, &m_dummy_record);

        if (rc != NRF_SUCCESS)
        {
            DBG_PRINTF("[FDS] Write ERR=%u\r\n", rc);
            fds_flag_write = false;
        }

        fds_wait_cnt = 0;

        while (fds_flag_write && fds_wait_cnt < 3000)  /* 3 second timeout */
        {
            nrf_pwr_mgmt_run();
            nrf_delay_ms(1);
            fds_wait_cnt++;
        }

        if (fds_flag_write)
        {
            DBG_PRINTF("[FDS] write TIMEOUT! forcing flag clear\r\n");
            fds_flag_write = false;
        }

        if ((rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH))
        {
            rc = fds_gc();
            APP_ERROR_CHECK(rc);
        }
        else
        {
            APP_ERROR_CHECK(rc);
        }

        NRF_LOG_FLUSH();
        goto cfg_load_start;
    }
}


/*==============================================================================
 * config_save - Persist current configuration to FDS
 *
 * Flow:
 * 1. If a previous FDS write is in progress, wait up to 3 seconds
 * 2. Fix magic number if needed
 * 3. If existing record found: fds_record_update()
 *    - On no-space: GC then retry
 * 4. If not found: fds_record_write() (new record)
 *
 * Write completion is asynchronous (fds_evt_handler). Post-processing
 * (power-off / sleep / reset) may follow.
 *============================================================================*/
void config_save( void )
{
    ret_code_t rc;
    fds_record_desc_t desc = { 0 };
    fds_find_token_t  tok  = { 0 };

    DBG_PRINTF("[CFG_SAVE] start\r\n");

    /* Wait for any previous FDS operation to complete */
    if (fds_flag_write)
    {
        uint32_t wait_cnt = 0;

        DBG_PRINTF("[CFG_SAVE] waiting for prev FDS op...\r\n");
        while (fds_flag_write && wait_cnt < 3000)
        {
            nrf_pwr_mgmt_run();
            nrf_delay_ms(1);
            wait_cnt++;
        }
        if (fds_flag_write)
        {
            DBG_PRINTF("[CFG_SAVE] TIMEOUT! forcing flag clear\r\n");
            fds_flag_write = false;
        }
    }

    if (m_config.magic_number != (uint32_t)CONFIG_MAGIC_NUMBER_VALUE)
    {
        m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;
    }

    memset((char *)&desc, 0, sizeof(desc));
    memset((char *)&tok, 0, sizeof(tok));
    rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok);
    DBG_PRINTF("[CFG_SAVE] find rc=%u\r\n", rc);

    if (rc == NRF_SUCCESS)
    {
        fds_flag_write = true;
        rc = fds_record_update(&desc, &m_dummy_record);
        DBG_PRINTF("[CFG_SAVE] update rc=%u\r\n", rc);

        if (rc == FDS_ERR_NO_SPACE_IN_FLASH)
        {
            fds_flag_write = false;
            rc = fds_gc();
            DBG_PRINTF("[CFG_SAVE] gc rc=%u, retry\r\n", rc);
            fds_flag_write = true;
            rc = fds_record_update(&desc, &m_dummy_record);
            DBG_PRINTF("[CFG_SAVE] retry rc=%u\r\n", rc);
        }

        if (rc != NRF_SUCCESS)
        {
            DBG_PRINTF("[CFG_SAVE] FAIL rc=%u\r\n", rc);
            fds_flag_write = false;
        }
    }
    else
    {
        DBG_PRINTF("[CFG_SAVE] not found, writing new\r\n");
        fds_flag_write = true;
        rc = fds_record_write(&desc, &m_dummy_record);
        DBG_PRINTF("[CFG_SAVE] write rc=%u\r\n", rc);

        if ( rc != NRF_SUCCESS )
        {
            DBG_PRINTF("[CFG_SAVE] FAIL rc=%u\r\n", rc);
            fds_flag_write = false;
        }
    }

    DBG_PRINTF("[CFG_SAVE] done\r\n");
}


/*==============================================================================
 * fs_set_value - Wrapper for config_load()
 *============================================================================*/
void fs_set_value(void)
{
    config_load();
}

/*==============================================================================
 * fs_storage_init - Initialise FDS
 *
 * Called once at boot:
 * 1. Register event handler
 * 2. Start FDS initialisation
 * 3. Wait for completion (up to 3 seconds)
 * 4. Verify flash stats
 *============================================================================*/
void fs_storage_init(void)
{
    ret_code_t rc;

    rc = fds_register(fds_evt_handler);
    APP_ERROR_CHECK(rc);

    rc = fds_init();
    APP_ERROR_CHECK(rc);

    wait_for_fds_ready();

    fds_stat_t stat = { 0 };
    rc = fds_stat(&stat);
    APP_ERROR_CHECK(rc);

    DBG_PRINTF("[FDS] OK\r\n");
}
