#pragma once

#include <nlohmann/json.hpp>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <phosphor-logging/lg2.hpp>

#include <format>
#include <fstream>
#include <ranges>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>

class JsonSerializer
{
  public:
    JsonSerializer(std::string path, nlohmann::json js = nlohmann::json()) :
        mfaConfPath(path), jsonData(std::move(js))
    {}

    inline auto stringSplitter()
    {
        return std::views::split('/') | std::views::transform([](auto&& sub) {
                   return std::string(sub.begin(), sub.end());
               });
    }
    nlohmann::json makeJson(const std::string& key, const std::string& value)
    {
        auto keys = key | stringSplitter();
        std::vector v(keys.begin(), keys.end());
        auto rv = v | std::views::reverse;
        nlohmann::json init;
        init[rv.front()] = value;
        auto newJson = std::reduce(rv.begin() + 1, rv.end(), init,
                                   [](auto sofar, auto currentKey) {
                                       nlohmann::json j;
                                       j[currentKey] = sofar;
                                       return j;
                                   });
        return newJson;
    }
    std::optional<nlohmann::json> getLeafNode(const std::string_view keyPath)
    {
        auto keys = keyPath | stringSplitter();
        nlohmann::json current = jsonData;
        for (auto key : keys)
        {
            if (!current.contains(key))
            {
                return std::nullopt;
            }
            current = current[key];
        }
        return current;
    }
    void serialize(std::string key, const std::string value)
    {
        jsonData.merge_patch(makeJson(key, value));
    }
    template <typename T>
    void deserialize(std::string key, T& value)
    {
        auto leaf = getLeafNode(key);
        if (leaf)
        {
            value = *leaf;
        }
    }
    void erase(std::string key)
    {
        if (jsonData.contains(key))
        {
            jsonData.erase(key);
        }
    }
    bool store()
    {
        std::filesystem::path dir =
            std::filesystem::path(mfaConfPath).parent_path();

        // Check if the directory exists, and create it if it does not
        if (!dir.string().empty() && !std::filesystem::exists(dir))
        {
            std::error_code ec;
            if (!std::filesystem::create_directories(dir, ec))
            {
                lg2::error("Unable to create directory {DIR}", "DIR",
                           dir.string());
                return false;
            }
        }
        std::ofstream file(mfaConfPath.data());
        if (file.is_open())
        {
            file << jsonData.dump(4); // Pretty print with 4 spaces
            file.close();
            return true;
        }
        else
        {
            lg2::error("Unable to open file {FILENAME}", "FILENAME",
                       mfaConfPath);
            return false;
        }
    }
    void load()
    {
        std::ifstream file(mfaConfPath.data());

        if (file.is_open())
        {
            file >> jsonData;
            file.close();
        }
        else
        {
            lg2::error("Unable to open file for reading {FILENAME}", "FILENAME",
                       mfaConfPath);
        }
    }

  private:
    const std::string mfaConfPath;
    nlohmann::json jsonData;
};