/*
 * This program source code file is part of the KTag project.
 *
 * 🛡 <https://ktag.clubk.club> 🃞
 *
 * Copyright © 2024-2026 Joseph P. Kearney and the KTag developers.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
 * details.
 *
 * There should be a copy of the GNU Affero General Public License in the LICENSE
 * file in the root of this repository.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <SystemK.h>
#include "Key_Value.h"

static const char *TAG = "NVM KV";
static const char *TEMP_FILE = "/usb/esp/temp.txt";

// Trims the whitespace from both ends of a string.
static void KV_Trim(char *str)
{
    char *start = str;
    char *end = str + strlen(str) - 1;
    while (*start && (*start == ' ' || *start == '\t'))
        start++;
    while (end > start && (*end == ' ' || *end == '\t' || *end == '\n'))
        end--;
    *(end + 1) = '\0';
    memmove(str, start, end - start + 2);
}

// Splits a line around the '=' into a key and value pair.
static int KV_Parse_Line(char *line, char *key, char *value)
{
    char *delimiter = strchr(line, '=');
    if (delimiter == NULL)
        return 0;
    *delimiter = '\0';
    strncpy(key, line, KV_MAX_KEY_LENGTH - 1);
    strncpy(value, delimiter + 1, KV_MAX_VALUE_LENGTH - 1);
    key[KV_MAX_KEY_LENGTH - 1] = '\0';
    value[KV_MAX_VALUE_LENGTH - 1] = '\0';
    KV_Trim(key);
    KV_Trim(value);
    return 1;
}

SystemKResult_T KV_Get_Value_string(const char *filename, const char *search_key, char *value)
{
    FILE *file = fopen(filename, "r");
    if (file == NULL)
    {
        KLOG_ERROR(TAG, "Couldn't open file %s for reading!", filename);
        return SYSTEMK_RESULT_FILE_NOT_FOUND;
    }

    char line[KV_MAX_LINE_LENGTH];
    char key[KV_MAX_KEY_LENGTH];

    while (fgets(line, sizeof(line), file))
    {
        if (KV_Parse_Line(line, key, value) && strcmp(key, search_key) == 0)
        {
            fclose(file);
            return SYSTEMK_RESULT_SUCCESS;
        }
    }

    fclose(file);
    KLOG_ERROR(TAG, "Couldn't find key %s in file %s!", search_key, filename);
    return SYSTEMK_RESULT_KEY_NOT_FOUND;
}

SystemKResult_T KV_Set_Value_string(const char *filename, const char *set_key, const char *set_value)
{
    FILE *file = fopen(filename, "r");
    if (file == NULL)
    {
        KLOG_ERROR(TAG, "Couldn't open file %s for reading!", filename);
        return SYSTEMK_RESULT_FILE_NOT_FOUND;
    }

    FILE *temp = fopen(TEMP_FILE, "w");
    if (temp == NULL)
    {
        fclose(file);
        KLOG_ERROR(TAG, "Couldn't open file %s for writing!", TEMP_FILE);
        return SYSTEMK_RESULT_WRITE_FAILED;
    }

    char line[KV_MAX_LINE_LENGTH];
    char line_copy[KV_MAX_LINE_LENGTH];
    char key[KV_MAX_KEY_LENGTH];
    char value[KV_MAX_VALUE_LENGTH];
    int found = 0;

    while (fgets(line, sizeof(line), file))
    {
        strncpy(line_copy, line, KV_MAX_LINE_LENGTH);
        line_copy[KV_MAX_LINE_LENGTH - 1] = '\0'; // Ensure null-termination

        if (KV_Parse_Line(line, key, value) && strcmp(key, set_key) == 0)
        {
            fprintf(temp, "%s = %s\n", set_key, set_value);
            found = 1;
        }
        else
        {
            fputs(line_copy, temp);
        }
    }

    if (!found)
    {
        fprintf(temp, "%s = %s\n", set_key, set_value);
    }

    fclose(file);
    fclose(temp);

    remove(filename);
    rename(TEMP_FILE, filename);

    return SYSTEMK_RESULT_SUCCESS;
}

SystemKResult_T KV_Get_Value_uint32(const char *filename, const char *search_key, uint32_t *value)
{
    char value_str[KV_MAX_VALUE_LENGTH];

    SystemKResult_T result = KV_Get_Value_string(filename, search_key, value_str);
    if (result != SYSTEMK_RESULT_SUCCESS)
    {
        return result;
    }

    char *endptr;
    // Reset errno to check for conversion errors.
    errno = 0;
    *value = strtoul(value_str, &endptr, 10);

    // Check for conversion errors
    if ((errno == ERANGE && *value == ULONG_MAX) || (errno != 0))
    {
        KLOG_ERROR(TAG, "Error converting %s for key %s to uint32_t!", value_str, search_key);
        return SYSTEMK_RESULT_WRONG_DATATYPE;
    }

    if (endptr == value_str)
    {
        KLOG_ERROR(TAG, "No digits were found in %s (for key %s)!", value_str, search_key);
        return SYSTEMK_RESULT_WRONG_DATATYPE;
    }

    if (*value > UINT32_MAX)
    {
        KLOG_ERROR(TAG, "Value %s (for key %s) exceeds uint32_t range!", value_str, search_key);
        return SYSTEMK_RESULT_OVERFLOW;
    }

    return SYSTEMK_RESULT_SUCCESS;
}

SystemKResult_T KV_Set_Value_uint32(const char *filename, const char *set_key, uint32_t *set_value)
{
    char value_str[KV_MAX_VALUE_LENGTH];
    int written = snprintf(value_str, KV_MAX_VALUE_LENGTH, "%lu", *set_value);

    if (written < 0 || written >= KV_MAX_VALUE_LENGTH)
    {
        KLOG_ERROR(TAG, "Error converting uint32_t to string for key %s!", set_key);
        return SYSTEMK_RESULT_WRONG_DATATYPE;
    }

    return KV_Set_Value_string(filename, set_key, value_str);
}

SystemKResult_T KV_Get_Value_uint8(const char *filename, const char *search_key, uint8_t *value)
{
    uint32_t value32 = 0;

    SystemKResult_T result = KV_Get_Value_uint32(filename, search_key, &value32);
    if (result != SYSTEMK_RESULT_SUCCESS)
    {
        return result;
    }

    // Check for conversion errors
    if (value32 > UINT8_MAX)
    {
        KLOG_ERROR(TAG, "Value %lu (for key %s) exceeds uint32_t range!", value32, search_key);
        return SYSTEMK_RESULT_OVERFLOW;
    }

    *value = (uint8_t)value32;

    return SYSTEMK_RESULT_SUCCESS;
}

SystemKResult_T KV_Set_Value_uint8(const char *filename, const char *set_key, uint8_t *set_value)
{
    char value_str[KV_MAX_VALUE_LENGTH];
    int written = snprintf(value_str, KV_MAX_VALUE_LENGTH, "%u", *set_value);

    if (written < 0 || written >= KV_MAX_VALUE_LENGTH)
    {
        KLOG_ERROR(TAG, "Error converting uint8_t to string for key %s!", set_key);
        return SYSTEMK_RESULT_WRONG_DATATYPE;
    }

    return KV_Set_Value_string(filename, set_key, value_str);
}
