#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <winhttp.h>

#include "platform/cloud_sync.hpp"

#include <fstream>
#include <format>
#include <memory>
#include <sstream>
#include <vector>

#pragma comment(lib, "winhttp.lib")

namespace poe2::platform {

namespace {

struct WinHttpCloser {
    void operator()(HINTERNET h) const noexcept {
        if (h) WinHttpCloseHandle(h);
    }
};

using UniqueWinHttp = std::unique_ptr<void, WinHttpCloser>;

std::wstring Utf8ToWide(std::string_view utf8Str) {
    if (utf8Str.empty()) return L"";
    int req = MultiByteToWideChar(CP_UTF8, 0, utf8Str.data(), static_cast<int>(utf8Str.size()), nullptr, 0);
    if (req <= 0) return L"";
    std::wstring wide(req, 0);
    MultiByteToWideChar(CP_UTF8, 0, utf8Str.data(), static_cast<int>(utf8Str.size()), wide.data(), req);
    return wide;
}

std::string WideToUtf8(std::wstring_view wideStr) {
    if (wideStr.empty()) return "";
    int req = WideCharToMultiByte(CP_UTF8, 0, wideStr.data(), static_cast<int>(wideStr.size()), nullptr, 0, nullptr, nullptr);
    if (req <= 0) return "";
    std::string utf8(req, 0);
    WideCharToMultiByte(CP_UTF8, 0, wideStr.data(), static_cast<int>(wideStr.size()), utf8.data(), req, nullptr, nullptr);
    return utf8;
}

std::string ReadFileContent(const std::filesystem::path& p) {
    if (!std::filesystem::exists(p)) return "";
    std::ifstream in(p, std::ios::binary);
    if (!in.is_open()) return "";
    std::ostringstream ss;
    ss << in.rdbuf();
    return ss.str();
}

} // namespace

CloudOffsetsSync::CloudOffsetsSync(
    std::filesystem::path localPath,
    std::string remoteUrl,
    int timeoutMs
)
    : localPath_(std::move(localPath))
    , remoteUrl_(std::move(remoteUrl))
    , timeoutMs_(timeoutMs)
{
}

std::string CloudOffsetsSync::ExtractVersionOrTimestamp(std::string_view tomlContent) {
    std::istringstream stream{std::string(tomlContent)};
    std::string line;
    while (std::getline(stream, line)) {
        // Trim
        size_t start = line.find_first_not_of(" \t\r\n");
        if (start == std::string::npos) continue;
        line = line.substr(start);

        if (line.starts_with("version") || line.starts_with("timestamp") || line.starts_with("patch")) {
            auto eq = line.find('=');
            if (eq != std::string::npos) {
                std::string val = line.substr(eq + 1);
                // Trim quotes and whitespace
                size_t vs = val.find_first_not_of(" \t\r\n\"'");
                size_t ve = val.find_last_not_of(" \t\r\n\"'");
                if (vs != std::string::npos && ve != std::string::npos && ve >= vs) {
                    return val.substr(vs, ve - vs + 1);
                }
            }
        }
    }
    return std::format("len_{}", tomlContent.size());
}

std::pair<bool, std::string> CloudOffsetsSync::FetchRemote(std::string_view overrideUrl) {
    std::string targetUrl = overrideUrl.empty() ? remoteUrl_ : std::string(overrideUrl);
    std::wstring wUrl = Utf8ToWide(targetUrl);

    URL_COMPONENTS urlComp = {0};
    urlComp.dwStructSize = sizeof(urlComp);
    urlComp.dwHostNameLength = static_cast<DWORD>(-1);
    urlComp.dwUrlPathLength = static_cast<DWORD>(-1);
    urlComp.dwExtraInfoLength = static_cast<DWORD>(-1);

    if (!WinHttpCrackUrl(wUrl.c_str(), static_cast<DWORD>(wUrl.size()), 0, &urlComp)) {
        return {false, "Loi phan tich dia chi URL (WinHttpCrackUrl failed)."};
    }

    std::wstring hostName(urlComp.lpszHostName, urlComp.dwHostNameLength);
    std::wstring urlPath(urlComp.lpszUrlPath, urlComp.dwUrlPathLength);
    if (urlComp.dwExtraInfoLength > 0) {
        urlPath.append(urlComp.lpszExtraInfo, urlComp.dwExtraInfoLength);
    }

    UniqueWinHttp hSession(WinHttpOpen(
        L"Poe2VisualTool-Sync/2.0 (Windows NT 10.0; Win64; x64)",
        WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
        WINHTTP_NO_PROXY_NAME,
        WINHTTP_NO_PROXY_BYPASS,
        0
    ));
    if (!hSession) {
        return {false, "Khong the khoi tao WinHTTP Session."};
    }

    WinHttpSetTimeouts(hSession.get(), timeoutMs_, timeoutMs_, timeoutMs_, timeoutMs_);

    UniqueWinHttp hConnect(WinHttpConnect(hSession.get(), hostName.c_str(), urlComp.nPort, 0));
    if (!hConnect) {
        return {false, "Khong the ket noi den may chu Cloud (WinHttpConnect failed)."};
    }

    DWORD reqFlags = (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0;
    UniqueWinHttp hRequest(WinHttpOpenRequest(
        hConnect.get(),
        L"GET",
        urlPath.c_str(),
        nullptr,
        WINHTTP_NO_REFERER,
        WINHTTP_DEFAULT_ACCEPT_TYPES,
        reqFlags
    ));
    if (!hRequest) {
        return {false, "Khong the tao HTTP Request."};
    }

    LPCWSTR headers = L"User-Agent: Poe2VisualTool-Sync/2.0\r\nAccept: text/plain, text/toml, application/toml\r\n";
    if (!WinHttpSendRequest(hRequest.get(), headers, static_cast<DWORD>(-1), WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) {
        return {false, "Khong the gui yeu cau toi may chu (WinHttpSendRequest failed)."};
    }

    if (!WinHttpReceiveResponse(hRequest.get(), nullptr)) {
        return {false, "Khong the nhan phan hoi tu may chu (WinHttpReceiveResponse failed)."};
    }

    DWORD statusCode = 0;
    DWORD scSize = sizeof(statusCode);
    if (!WinHttpQueryHeaders(
            hRequest.get(),
            WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
            WINHTTP_HEADER_NAME_BY_INDEX,
            &statusCode,
            &scSize,
            WINHTTP_NO_HEADER_INDEX)) {
        return {false, "Khong the doc ma trang thai HTTP."};
    }

    if (statusCode != 200) {
        return {false, std::format("May chu phan hoi ma loi HTTP {}", statusCode)};
    }

    std::string responseBody;
    DWORD bytesAvailable = 0;
    while (WinHttpQueryDataAvailable(hRequest.get(), &bytesAvailable) && bytesAvailable > 0) {
        std::vector<char> buffer(bytesAvailable);
        DWORD bytesRead = 0;
        if (WinHttpReadData(hRequest.get(), buffer.data(), bytesAvailable, &bytesRead) && bytesRead > 0) {
            responseBody.append(buffer.data(), bytesRead);
        } else {
            break;
        }
    }

    return {true, responseBody};
}

SyncResult CloudOffsetsSync::CheckAndUpdate() {
    SyncResult res;
    std::string localContent = ReadFileContent(localPath_);
    res.localVersion = ExtractVersionOrTimestamp(localContent);

    auto [ok, remoteData] = FetchRemote();
    if (!ok) {
        res.success = false;
        res.updated = false;
        res.message = remoteData; // Error message
        return res;
    }

    res.remoteVersion = ExtractVersionOrTimestamp(remoteData);

    // Basic validity check of remote TOML content
    if (!remoteData.contains("[") || remoteData.size() < 20) {
        res.success = false;
        res.updated = false;
        res.message = "Noi dung remote offsets khong hop le hoac bi hong.";
        return res;
    }

    if (res.localVersion != res.remoteVersion || localContent.empty()) {
        std::error_code ec;
        if (localPath_.has_parent_path()) {
            std::filesystem::create_directories(localPath_.parent_path(), ec);
        }
        std::ofstream out(localPath_, std::ios::binary | std::ios::trunc);
        if (out.is_open()) {
            out.write(remoteData.data(), remoteData.size());
            out.close();
            res.success = true;
            res.updated = true;
            res.message = std::format("Cap nhat offsets thanh cong tu cloud: {} -> {}", res.localVersion, res.remoteVersion);
        } else {
            res.success = false;
            res.updated = false;
            res.message = "Khong the ghi tep offsets.toml vao o dia.";
        }
    } else {
        res.success = true;
        res.updated = false;
        res.message = std::format("Offsets da o phien ban moi nhat ({})", res.localVersion);
    }

    return res;
}

} // namespace poe2::platform
