#include "adapters/persistence/FileConfigPersistence.h"

#include <filesystem>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "application/config/persistence/ConfigCodec.h"
#include "application/config/persistence/ConfigParse.h"
#include "adapters/persistence/WindowsConfigStorage.h"
#include "foundation/diagnostics/Logger.h"

namespace
{
    using Values = std::unordered_map<std::string, std::string>;

    struct DecodedState final
    {
        ta::application::ConfigApplicationState state;
        bool success = false;
        std::string error;
    };

    bool migrate(Values& values, std::uint32_t& version, std::string& error)
    {
        if (const auto legacy_player_chams = values.find("chams.player"); legacy_player_chams != values.end())
        {
            values.try_emplace("chams.player_chams", legacy_player_chams->second);
            values.erase(legacy_player_chams);
        }

        if (const auto legacy_fly_up = values.find("player.fly_up_hotkey"); legacy_fly_up != values.end())
        {
            values.try_emplace("player.up", legacy_fly_up->second);
            values.erase(legacy_fly_up);
        }

        if (const auto legacy_fly_down = values.find("player.fly_down_hotkey"); legacy_fly_down != values.end())
        {
            values.try_emplace("player.down", legacy_fly_down->second);
            values.erase(legacy_fly_down);
        }

        if (const auto watermark_text = values.find("watermark.text"); watermark_text != values.end())
        {
            if (watermark_text->second == "TarkovAddonsRefactor for 1.0.6.0.46010" ||
                watermark_text->second == "TarkovAddonsRefactor")
            {
                watermark_text->second = "TarkovAddonsRefactored";
            }
        }

        if (version == 0)
            version = 1;
        if (version > ta::application::current_config_schema_version)
        {
            error = "configuration schema is newer than this build";
            return false;
        }

        while (version < ta::application::current_config_schema_version)
        {
            switch (version)
            {
            case 1:
                // Schema 2 makes the previously implicit schema version explicit.
                ++version;
                break;
            case 2:
                // Schema 3 renames the vertical fly-control configuration keys.
                ++version;
                break;
            default:
                error = "no migration path for schema " + std::to_string(version);
                return false;
            }
        }
        return true;
    }

    DecodedState decode_state(const std::string& content)
    {
        DecodedState result{};
        auto decoded = ta::application::config::ConfigCodec::decode(content);
        if (!decoded.success)
        {
            result.error = "invalid state at line " + std::to_string(decoded.error_line) + ": " + decoded.error;
            return result;
        }

        std::uint32_t version = 1;
        if (const auto it = decoded.values.find("schema_version"); it != decoded.values.end())
        {
            const auto parsed = ta::application::config::parse::integer(it->second);
            if (!parsed || *parsed <= 0)
            {
                result.error = "invalid schema_version";
                return result;
            }
            version = static_cast<std::uint32_t>(*parsed);
            decoded.values.erase(it);
        }

        if (!migrate(decoded.values, version, result.error))
            return result;

        ta::application::ConfigApplicationState state{};
        state.schema_version = version;
        state.values = std::move(decoded.values);
        if (const auto it = state.values.find("menu_key"); it != state.values.end())
        {
            const auto parsed = ta::application::config::parse::integer(it->second);
            if (!parsed)
            {
                result.error = "invalid menu_key";
                return result;
            }
            state.menu_key = *parsed;
            state.values.erase(it);
        }
        if (const auto it = state.values.find("language"); it != state.values.end())
        {
            state.language_id = it->second;
            state.values.erase(it);
        }

        result.state = std::move(state);
        result.success = true;
        return result;
    }

    std::optional<DecodedState> read_decoded(const std::filesystem::path& path)
    {
        const auto content = ta::adapters::persistence::WindowsConfigStorage::read_file(path);
        if (!content)
            return std::nullopt;
        return decode_state(*content);
    }
}

ta::application::ConfigStateLoadResult ta::adapters::persistence::FileConfigPersistence::load_state() const
{
    ta::application::ConfigStateLoadResult result{};
    const auto state_path = WindowsConfigStorage::state_path();
    if (!state_path)
    {
        ta::foundation::logger::error("CONFIG", "state path unavailable");
        return result;
    }

    std::error_code ec;
    const bool state_exists = std::filesystem::exists(*state_path, ec) && !ec;

    if (const auto decoded = read_decoded(*state_path))
    {
        if (decoded->success)
        {
            result.status = ta::application::ConfigStateLoadStatus::Loaded;
            result.state = decoded->state;
            return result;
        }

        ta::foundation::logger::error("CONFIG", decoded->error + " (" + state_path->string() + ")");
    }
    else if (state_exists)
    {
        ta::foundation::logger::error("CONFIG", "state read failed | path=" + state_path->string());
    }

    result.status = state_exists
        ? ta::application::ConfigStateLoadStatus::Failed
        : ta::application::ConfigStateLoadStatus::Missing;
    return result;
}

bool ta::adapters::persistence::FileConfigPersistence::save_state(
    const ta::application::ConfigApplicationState& state) const
{
    const auto path = WindowsConfigStorage::state_path();
    if (!path)
        return false;

    std::vector<ta::application::config::ConfigCodec::Entry> entries;
    entries.reserve(state.values.size() + 3U);
    entries.push_back({"schema_version", std::to_string(ta::application::current_config_schema_version)});
    entries.push_back({"language", state.language_id});
    entries.push_back({"menu_key", std::to_string(state.menu_key)});
    for (const auto& [key, value] : state.values)
    {
        if (key.empty() || key == "schema_version" || key == "language" || key == "menu_key")
            continue;
        entries.push_back({key, value});
    }

    return WindowsConfigStorage::write_state(
        *path,
        ta::application::config::ConfigCodec::encode(std::move(entries)));
}
