#include "NetworkClient.h"
#include "CertPinner.h"
#include "json/json.hpp"
#include <cstdio>
#include <cstring>
#include <sstream>

using json = nlohmann::json;

// ─── Singleton ────────────────────────────────────────

NetworkClient* NetworkClient::Get()
{
    static NetworkClient instance;
    return &instance;
}

// ─── Connect / Close ──────────────────────────────────

HINTERNET NetworkClient::Connect()
{
    HINTERNET hSession = WinHttpOpen(
        L"skeet_loader/1.0",
        WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
        WINHTTP_NO_PROXY_NAME,
        WINHTTP_NO_PROXY_BYPASS,
        0);

    if (!hSession)
    {
        printf("[NetworkClient] WinHttpOpen failed: 0x%lx\n", GetLastError());
        return nullptr;
    }

    // Set timeouts
    WinHttpSetTimeouts(hSession, TIMEOUT_MS, TIMEOUT_MS, TIMEOUT_MS, TIMEOUT_MS);

    HINTERNET hConnect = WinHttpConnect(hSession, SERVER_HOST, SERVER_PORT, 0);
    if (!hConnect)
    {
        printf("[NetworkClient] WinHttpConnect failed: 0x%lx\n", GetLastError());
        WinHttpCloseHandle(hSession);
        return nullptr;
    }

    return hConnect;
}

void NetworkClient::Close(HINTERNET hSession, HINTERNET hConnect)
{
    if (hConnect) WinHttpCloseHandle(hConnect);
    if (hSession) WinHttpCloseHandle(hSession);
}

// ─── Send Request ─────────────────────────────────────

NetworkClient::HttpResponse NetworkClient::SendRequest(
    const std::wstring& endpoint,
    const std::string& jsonBody,
    const std::string& bearerToken)
{
    HttpResponse resp;

    HINTERNET hSession = nullptr;
    HINTERNET hConnect = nullptr;
    HINTERNET hRequest = nullptr;
    DWORD flags = 0;
    DWORD statusCode = 0;
    DWORD statusCodeSize = 0;
    DWORD bytesRead = 0;
    LPCWSTR headers = nullptr;

    hSession = WinHttpOpen(
        L"skeet_loader/1.0",
        WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
        WINHTTP_NO_PROXY_NAME,
        WINHTTP_NO_PROXY_BYPASS,
        0);
    if (!hSession) { printf("[NetworkClient] WinHttpOpen failed\n"); return resp; }
    WinHttpSetTimeouts(hSession, TIMEOUT_MS, TIMEOUT_MS, TIMEOUT_MS, TIMEOUT_MS);

    hConnect = WinHttpConnect(hSession, SERVER_HOST, SERVER_PORT, 0);
    if (!hConnect) { printf("[NetworkClient] WinHttpConnect failed\n"); goto cleanup; }

    flags = SERVER_USE_SSL ? WINHTTP_FLAG_SECURE : 0;
    hRequest = WinHttpOpenRequest(
        hConnect,
        L"POST",
        endpoint.c_str(),
        nullptr,
        WINHTTP_NO_REFERER,
        WINHTTP_DEFAULT_ACCEPT_TYPES,
        flags);
    if (!hRequest) { printf("[NetworkClient] WinHttpOpenRequest failed\n"); goto cleanup; }

    // Set content type + optional bearer token
    headers = L"Content-Type: application/json\r\n";
    if (!bearerToken.empty())
    {
        std::string authHeader = "Authorization: Bearer " + bearerToken + "\r\n";
        std::wstring wAuthHeader(authHeader.begin(), authHeader.end());
        std::wstring allHeaders = std::wstring(headers) + wAuthHeader;
        WinHttpAddRequestHeaders(hRequest, allHeaders.c_str(),
            (DWORD)allHeaders.length(), WINHTTP_ADDREQ_FLAG_ADD);
    }
    else
    {
        WinHttpAddRequestHeaders(hRequest, headers, (DWORD)wcslen(headers),
            WINHTTP_ADDREQ_FLAG_ADD);
    }

    // Send
    if (!WinHttpSendRequest(
            hRequest,
            WINHTTP_NO_ADDITIONAL_HEADERS, 0,
            (LPVOID)jsonBody.c_str(), (DWORD)jsonBody.size(),
            (DWORD)jsonBody.size(), 0))
    {
        DWORD err = GetLastError();
        printf("[NetworkClient] WinHttpSendRequest failed: 0x%lx\n", err);
        if (err == ERROR_WINHTTP_SECURE_FAILURE)
            printf("[NetworkClient] > SSL error (cert pinning or TLS issue)\n");
        goto cleanup;
    }

    // Receive response
    if (!WinHttpReceiveResponse(hRequest, nullptr))
    {
        printf("[NetworkClient] WinHttpReceiveResponse failed: 0x%lx\n", GetLastError());
        goto cleanup;
    }

    // Cert pinning check (only for HTTPS)
    if (SERVER_USE_SSL)
    {
        if (!CertPinner::VerifyServerCert(hRequest))
        {
            printf("[NetworkClient] CertPinner rejected connection!\n");
            resp.statusCode = 0;
            goto cleanup;
        }
    }

    // Get status code
    statusCode = 0;
    statusCodeSize = sizeof(statusCode);
    WinHttpQueryHeaders(hRequest,
        WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
        WINHTTP_HEADER_NAME_BY_INDEX,
        &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX);
    resp.statusCode = statusCode;

    // Read response body
    bytesRead = 0;
    char buffer[4096];
    while (WinHttpReadData(hRequest, buffer, sizeof(buffer) - 1, &bytesRead) && bytesRead > 0)
    {
        buffer[bytesRead] = '\0';
        resp.body += buffer;
    }

cleanup:
    if (hRequest) WinHttpCloseHandle(hRequest);
    if (hConnect) WinHttpCloseHandle(hConnect);
    if (hSession) WinHttpCloseHandle(hSession);
    return resp;
}

// ─── Send GET Request (for /api/status) ───────────────

static std::pair<DWORD, std::string> SendGetRequest(const std::wstring& endpoint)
{
    DWORD statusCode = 0;
    std::string body;

    HINTERNET hSession = WinHttpOpen(
        L"skeet_loader/1.0",
        WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
        WINHTTP_NO_PROXY_NAME,
        WINHTTP_NO_PROXY_BYPASS, 0);
    if (!hSession) return { 0, "" };
    WinHttpSetTimeouts(hSession, 10000, 10000, 10000, 10000);

    HINTERNET hConnect = WinHttpConnect(hSession, SERVER_HOST, SERVER_PORT, 0);
    if (!hConnect) { WinHttpCloseHandle(hSession); return { 0, "" }; }

    DWORD flags = SERVER_USE_SSL ? WINHTTP_FLAG_SECURE : 0;
    HINTERNET hRequest = WinHttpOpenRequest(
        hConnect, L"GET", endpoint.c_str(),
        nullptr, WINHTTP_NO_REFERER,
        WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
    if (!hRequest) { WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return { 0, "" }; }

    if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
            WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
    {
        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);
        return { 0, "" };
    }

    if (!WinHttpReceiveResponse(hRequest, nullptr))
    {
        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        WinHttpCloseHandle(hSession);
        return { 0, "" };
    }

    if (SERVER_USE_SSL) CertPinner::VerifyServerCert(hRequest);

    DWORD sc = 0, scSize = sizeof(sc);
    WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
        WINHTTP_HEADER_NAME_BY_INDEX, &sc, &scSize, WINHTTP_NO_HEADER_INDEX);
    statusCode = sc;

    DWORD bytesRead = 0;
    char buffer[4096];
    while (WinHttpReadData(hRequest, buffer, sizeof(buffer) - 1, &bytesRead) && bytesRead > 0)
    {
        buffer[bytesRead] = '\0';
        body += buffer;
    }

    WinHttpCloseHandle(hRequest);
    WinHttpCloseHandle(hConnect);
    WinHttpCloseHandle(hSession);
    return { statusCode, body };
}

// ─── JSON Builders ────────────────────────────────────

std::string NetworkClient::BuildLoginBody(
    const std::string& username,
    const std::string& passwordHash,
    const std::string& cpuHash,
    const std::string& boardHash,
    const std::string& diskHash,
    const std::string& macHash)
{
    json j;
    j["username"] = username;
    j["password_hash"] = passwordHash;  // SHA-256(password) — raw password never sent
    j["hwid_components"]["cpu"] = cpuHash;
    j["hwid_components"]["board"] = boardHash;
    j["hwid_components"]["disk"] = diskHash;
    j["hwid_components"]["mac"] = macHash;
    return j.dump();
}

// ─── Public API ───────────────────────────────────────

StatusResult NetworkClient::CheckStatus()
{
    StatusResult result;

    auto [statusCode, body] = SendGetRequest(L"/api/status");

    if (statusCode == 0)
    {
        result.online = false;
        result.errorMessage = "Server unreachable";
        return result;
    }

    try {
        auto j = json::parse(body);
        if (j.contains("status") && j["status"].get<std::string>() == "ok")
            result.online = true;
        else
            result.errorMessage = "Invalid status response";
    } catch (...) {
        result.errorMessage = "Failed to parse status response";
    }

    return result;
}

LoginResult NetworkClient::Login(
    const std::string& username,
    const std::string& passwordHash,
    const std::string& cpuHash,
    const std::string& boardHash,
    const std::string& diskHash,
    const std::string& macHash)
{
    LoginResult result;

    std::string body = BuildLoginBody(username, passwordHash, cpuHash, boardHash, diskHash, macHash);
    auto resp = SendRequest(L"/api/login", body);

    if (resp.statusCode == 0)
    {
        result.errorCode = "NETWORK_ERROR";
        result.errorMessage = "Server unreachable";
        return result;
    }

    try {
        auto j = json::parse(resp.body);
        result.success = j.value("success", false);

        if (result.success)
        {
            result.accessToken = j.value("access_token", "");
            result.refreshToken = j.value("refresh_token", "");
            result.username = j.value("username", "");
            result.matchRate = j.value("match_rate", 0.0);
        }
        else
        {
            result.errorCode = j.value("error", "UNKNOWN");
            result.errorMessage = j.value("message", "");
            result.retryAfter = j.value("retry_after", 0);
        }
    } catch (const std::exception& e) {
        printf("[NetworkClient] login parse error: %s\n", e.what());
        result.errorCode = "PARSE_ERROR";
        result.errorMessage = "Failed to parse server response";
    }

    return result;
}

RefreshResult NetworkClient::RefreshToken(
    const std::string& refreshToken,
    const std::string& cpuHash,
    const std::string& boardHash,
    const std::string& diskHash,
    const std::string& macHash)
{
    RefreshResult result;

    // Build JSON body
    json j;
    j["refresh_token"] = refreshToken;
    j["hwid_components"]["cpu"] = cpuHash;
    j["hwid_components"]["board"] = boardHash;
    j["hwid_components"]["disk"] = diskHash;
    j["hwid_components"]["mac"] = macHash;
    std::string body = j.dump();

    auto resp = SendRequest(L"/api/refresh", body);

    if (resp.statusCode == 0)
    {
        result.errorCode = "NETWORK_ERROR";
        return result;
    }

    try {
        auto respJson = json::parse(resp.body);
        result.success = respJson.value("success", false);

        if (result.success)
        {
            result.accessToken = respJson.value("access_token", "");
            result.refreshToken = respJson.value("refresh_token", "");
        }
        else
        {
            result.errorCode = respJson.value("error", "UNKNOWN");
        }
    } catch (const std::exception& e) {
        printf("[NetworkClient] refresh parse error: %s\n", e.what());
        result.errorCode = "PARSE_ERROR";
    }

    return result;
}
