#include "assets/settings_document.h"

#include <algorithm>
#include <cctype>
#include <cmath>

namespace pp::assets {

namespace {

[[nodiscard]] bool is_valid_key_char(char value) noexcept
{
    const auto ch = static_cast<unsigned char>(value);
    return std::isalnum(ch) != 0 || value == '_' || value == '-' || value == '.';
}

}

std::size_t SettingsDocument::size() const noexcept
{
    return entries_.size();
}

bool SettingsDocument::empty() const noexcept
{
    return entries_.empty();
}

bool SettingsDocument::has(std::string_view key) const noexcept
{
    return find_entry(key) != entries_.end();
}

const std::vector<SettingsEntry>& SettingsDocument::entries() const noexcept
{
    return entries_;
}

pp::foundation::Status SettingsDocument::set(std::string_view key, SettingsValue value)
{
    const auto key_status = validate_settings_key(key);
    if (!key_status.ok()) {
        return key_status;
    }

    const auto value_status = validate_settings_value(value);
    if (!value_status.ok()) {
        return value_status;
    }

    auto found = find_entry(key);
    if (found != entries_.end()) {
        found->value = value;
        return pp::foundation::Status::success();
    }

    if (entries_.size() >= max_settings_entries) {
        return pp::foundation::Status::out_of_range("settings entry count exceeds the configured limit");
    }

    entries_.push_back(SettingsEntry {
        .key = std::string(key),
        .value = value,
    });
    return pp::foundation::Status::success();
}

pp::foundation::Result<SettingsValue> SettingsDocument::get(std::string_view key) const
{
    const auto key_status = validate_settings_key(key);
    if (!key_status.ok()) {
        return pp::foundation::Result<SettingsValue>::failure(key_status);
    }

    const auto found = find_entry(key);
    if (found == entries_.end()) {
        return pp::foundation::Result<SettingsValue>::failure(
            pp::foundation::Status::out_of_range("settings key was not found"));
    }

    return pp::foundation::Result<SettingsValue>::success(found->value);
}

pp::foundation::Status SettingsDocument::unset(std::string_view key) noexcept
{
    const auto key_status = validate_settings_key(key);
    if (!key_status.ok()) {
        return key_status;
    }

    const auto found = find_entry(key);
    if (found == entries_.end()) {
        return pp::foundation::Status::out_of_range("settings key was not found");
    }

    entries_.erase(found);
    return pp::foundation::Status::success();
}

void SettingsDocument::clear() noexcept
{
    entries_.clear();
}

std::vector<SettingsEntry>::iterator SettingsDocument::find_entry(std::string_view key) noexcept
{
    return std::find_if(
        entries_.begin(),
        entries_.end(),
        [key](const SettingsEntry& entry) {
            return entry.key == key;
        });
}

std::vector<SettingsEntry>::const_iterator SettingsDocument::find_entry(std::string_view key) const noexcept
{
    return std::find_if(
        entries_.begin(),
        entries_.end(),
        [key](const SettingsEntry& entry) {
            return entry.key == key;
        });
}

pp::foundation::Status validate_settings_key(std::string_view key) noexcept
{
    if (key.empty()) {
        return pp::foundation::Status::invalid_argument("settings key must not be empty");
    }

    if (key.size() > max_settings_key_length) {
        return pp::foundation::Status::out_of_range("settings key length exceeds the configured limit");
    }

    if (key.front() == '.' || key.back() == '.') {
        return pp::foundation::Status::invalid_argument("settings key must not start or end with a dot");
    }

    for (const auto ch : key) {
        if (!is_valid_key_char(ch)) {
            return pp::foundation::Status::invalid_argument("settings key contains an unsupported character");
        }
    }

    return pp::foundation::Status::success();
}

pp::foundation::Status validate_settings_value(const SettingsValue& value) noexcept
{
    if (const auto* string_value = std::get_if<std::string>(&value)) {
        if (string_value->size() > max_settings_string_length) {
            return pp::foundation::Status::out_of_range("settings string length exceeds the configured limit");
        }
    }

    if (const auto* double_value = std::get_if<double>(&value)) {
        if (!std::isfinite(*double_value)) {
            return pp::foundation::Status::invalid_argument("settings floating point value must be finite");
        }
    }

    return pp::foundation::Status::success();
}

const char* settings_value_type_name(const SettingsValue& value) noexcept
{
    if (std::holds_alternative<bool>(value)) {
        return "bool";
    }
    if (std::holds_alternative<std::int64_t>(value)) {
        return "int64";
    }
    if (std::holds_alternative<double>(value)) {
        return "double";
    }
    if (std::holds_alternative<std::string>(value)) {
        return "string";
    }

    return "unknown";
}

}
