#pragma once
// TryhardAuth C++17 client. Dependencies: libcurl and nlohmann/json.
#include <curl/curl.h>
#include <nlohmann/json.hpp>
#include <fstream>
#include <memory>
#include <string>
#include <utility>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#ifdef _MSC_VER
#pragma comment(lib, "Advapi32.lib")
#endif
#endif

namespace TryhardAuth {
struct result {
    bool success = false;
    std::string message;
    nlohmann::json info;
};

inline std::string machine_hwid() {
#ifdef _WIN32
    char buffer[256] = {};
    DWORD size = sizeof(buffer);
    if (RegGetValueA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", "MachineGuid",
        RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, nullptr, buffer, &size) == ERROR_SUCCESS)
        return std::string(buffer);
#else
    std::ifstream input("/etc/machine-id");
    std::string value;
    if (std::getline(input, value)) return value;
#endif
    return {}; // Fail closed: never substitute a shared generic device ID.
}

class api {
public:
    result response;
    api(std::string name, std::string ownerid, std::string secret, std::string version,
        std::string url = "https://tryhardauth.cataclysmmc.lat/api/v1")
        : name_(std::move(name)), ownerid_(std::move(ownerid)), secret_(std::move(secret)),
          version_(std::move(version)), url_(std::move(url)) {
        while (!url_.empty() && url_.back() == '/') url_.pop_back();
    }

    bool init(const std::string& binary_hash = "") {
        session_.clear();
        if (name_.empty() || ownerid_.empty() || secret_.empty() || version_.empty())
            return fail("name, ownerid, secret and version are required.");
        nlohmann::json payload = {{"name", name_}, {"ownerid", ownerid_}, {"secret", secret_}, {"version", version_}};
        if (!binary_hash.empty()) payload["hash"] = binary_hash;
        if (!post("/init", payload)) return false;
        if (!last_.contains("sessionid") || !last_["sessionid"].is_string() || last_["sessionid"].get<std::string>().empty())
            return fail("The server did not return a valid session.");
        session_ = last_["sessionid"].get<std::string>();
        return true;
    }

    bool license(const std::string& key, const std::string& hwid = machine_hwid()) {
        if (session_.empty()) return fail("Call init() before license().");
        if (key.empty() || hwid.empty()) return fail("A license and a stable device HWID are required.");
        return post("/license", {{"sessionid", session_}, {"name", name_}, {"ownerid", ownerid_}, {"key", key}, {"hwid", hwid}});
    }

    bool check() {
        if (session_.empty()) return fail("Call init() before check().");
        return post("/check", {{"sessionid", session_}, {"name", name_}, {"ownerid", ownerid_}});
    }

private:
    std::string name_, ownerid_, secret_, version_, url_, session_;
    nlohmann::json last_;
    bool fail(const std::string& message) { response = {false, message, {}}; return false; }
    static size_t write(char* data, size_t size, size_t count, void* target) noexcept {
        auto& text = *static_cast<std::string*>(target);
        if (size != 0 && count > 1024 * 1024 / size) return 0;
        const auto length = size * count;
        if (text.size() + length > 1024 * 1024) return 0;
        try { text.append(data, length); return length; } catch (...) { return 0; }
    }
    bool post(const std::string& endpoint, const nlohmann::json& payload) {
        response = {}; last_ = {};
        static const CURLcode initialized = curl_global_init(CURL_GLOBAL_DEFAULT);
        if (initialized != CURLE_OK) return fail("Could not initialize libcurl.");
        std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> handle(curl_easy_init(), curl_easy_cleanup);
        if (!handle) return fail("Could not create the HTTP client.");
        std::unique_ptr<curl_slist, decltype(&curl_slist_free_all)> headers(curl_slist_append(nullptr, "Content-Type: application/json"), curl_slist_free_all);
        if (!headers) return fail("Could not create the request headers.");
        const std::string address = url_ + endpoint;
        const std::string body = payload.dump();
        std::string output;
        curl_easy_setopt(handle.get(), CURLOPT_URL, address.c_str());
        curl_easy_setopt(handle.get(), CURLOPT_PROTOCOLS_STR, "http,https");
        curl_easy_setopt(handle.get(), CURLOPT_POST, 1L);
        curl_easy_setopt(handle.get(), CURLOPT_HTTPHEADER, headers.get());
        curl_easy_setopt(handle.get(), CURLOPT_POSTFIELDS, body.c_str());
        curl_easy_setopt(handle.get(), CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body.size()));
        curl_easy_setopt(handle.get(), CURLOPT_WRITEFUNCTION, &api::write);
        curl_easy_setopt(handle.get(), CURLOPT_WRITEDATA, &output);
        curl_easy_setopt(handle.get(), CURLOPT_CONNECTTIMEOUT, 10L);
        curl_easy_setopt(handle.get(), CURLOPT_TIMEOUT, 25L);
        curl_easy_setopt(handle.get(), CURLOPT_NOSIGNAL, 1L);
        curl_easy_setopt(handle.get(), CURLOPT_SSL_VERIFYPEER, 1L);
        curl_easy_setopt(handle.get(), CURLOPT_SSL_VERIFYHOST, 2L);
        const auto status = curl_easy_perform(handle.get());
        if (status != CURLE_OK) return fail(std::string("Request failed: ") + curl_easy_strerror(status));
        long http_status = 0;
        curl_easy_getinfo(handle.get(), CURLINFO_RESPONSE_CODE, &http_status);
        try {
            last_ = nlohmann::json::parse(output);
            if (!last_.is_object()) return fail("Invalid server response.");
            response.success = http_status >= 200 && http_status < 300 && last_.value("success", false);
            response.message = last_.value("message", std::string("The request was rejected."));
            response.info = last_.value("info", nlohmann::json::object());
            return response.success;
        } catch (const nlohmann::json::exception&) { return fail("The server response is not valid JSON."); }
    }
};
} // namespace TryhardAuth
