#pragma once

#include <Windows.h>

#include <algorithm>
#include <filesystem>
#include <fstream>
#include <limits>
#include <mutex>
#include <optional>
#include <string>

#include "adapters/windows/storage/RuntimeStorageLayout.h"
#include "foundation/io/FileSystemUtils.h"

namespace ta::adapters::persistence
{
class WindowsConfigStorage
{
public:
    [[nodiscard]] static std::optional<std::filesystem::path> state_path()
    {
        const auto layout = ta::adapters::windows::storage::snapshot();
        return layout ? std::optional(layout->root / L"config.cfg") : std::nullopt;
    }

    [[nodiscard]] static std::optional<std::string> read_file(
        const std::filesystem::path& path,
        const std::size_t max_bytes = 4U * 1024U * 1024U)
    {
        std::error_code ec;
        const std::uintmax_t size = std::filesystem::file_size(path, ec);
        if (ec || size > max_bytes ||
            size > static_cast<std::uintmax_t>((std::numeric_limits<std::streamsize>::max)()))
        {
            return std::nullopt;
        }

        std::ifstream input(path, std::ios::binary);
        if (!input.good())
            return std::nullopt;

        std::string content(static_cast<std::size_t>(size), '\0');
        if (!content.empty())
            input.read(content.data(), static_cast<std::streamsize>(content.size()));

        if (input.bad() || static_cast<std::size_t>(input.gcount()) != content.size())
            return std::nullopt;
        return content;
    }

    [[nodiscard]] static bool write_state(
        const std::filesystem::path& path,
        const std::string& content)
    {
        static std::mutex write_mutex;
        std::scoped_lock lock(write_mutex);
        return write_file_atomic_unlocked(path, content);
    }

private:
    [[nodiscard]] static bool write_file_atomic_unlocked(
        const std::filesystem::path& path,
        const std::string& content)
    {
        if (!ta::foundation::io::ensure_parent_directory(path))
            return false;

        std::error_code ec;
        std::filesystem::path temporary = path;
        temporary += L".tmp";
        std::filesystem::remove(temporary, ec);

        const std::wstring temporary_native = temporary.wstring();
        const std::wstring destination_native = path.wstring();
        HANDLE file = CreateFileW(
            temporary_native.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
            FILE_ATTRIBUTE_TEMPORARY, nullptr);
        if (file == INVALID_HANDLE_VALUE)
            return false;

        bool success = true;
        std::size_t offset = 0;
        while (offset < content.size())
        {
            const std::size_t remaining = content.size() - offset;
            const DWORD chunk = static_cast<DWORD>((std::min)(
                remaining,
                static_cast<std::size_t>((std::numeric_limits<DWORD>::max)())));
            DWORD written = 0;
            if (!WriteFile(file, content.data() + offset, chunk, &written, nullptr) || written != chunk)
            {
                success = false;
                break;
            }
            offset += written;
        }

        if (success && !FlushFileBuffers(file))
            success = false;
        if (!CloseHandle(file))
            success = false;

        if (success)
        {
            success = MoveFileExW(
                temporary_native.c_str(), destination_native.c_str(),
                MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != FALSE;
        }

        if (!success)
            std::filesystem::remove(temporary, ec);
        return success;
    }
};
}
