#include "WinHttpClient.h"
#include <iostream>
#include <sstream>
#include <random>
#include <iomanip>

#ifdef _WIN32
#pragma comment(lib, "winhttp.lib")
#endif

namespace secondvoice {

WinHttpClient::WinHttpClient() {
#ifdef _WIN32
    hSession_ = WinHttpOpen(
        L"SecondVoice/1.0",
        WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
        WINHTTP_NO_PROXY_NAME,
        WINHTTP_NO_PROXY_BYPASS,
        0
    );
    if (!hSession_) {
        std::cerr << "WinHttpOpen failed: " << GetLastError() << std::endl;
    }
#endif
}

WinHttpClient::~WinHttpClient() {
#ifdef _WIN32
    if (hSession_) {
        WinHttpCloseHandle(hSession_);
    }
#endif
}

std::string WinHttpClient::generateBoundary() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, 15);
    const char* hex = "0123456789abcdef";

    std::string boundary = "----WebKitFormBoundary";
    for (int i = 0; i < 16; i++) {
        boundary += hex[dis(gen)];
    }
    return boundary;
}

#ifdef _WIN32
static std::wstring toWideString(const std::string& str) {
    if (str.empty()) return L"";
    int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
    std::wstring result(size - 1, 0);
    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &result[0], size);
    return result;
}
#endif

std::optional<HttpResponse> WinHttpClient::postJson(
    const std::string& host,
    const std::string& path,
    const std::string& jsonBody,
    const std::map<std::string, std::string>& headers) {

#ifdef _WIN32
    if (!hSession_) {
        return std::nullopt;
    }

    std::wstring wHost = toWideString(host);
    HINTERNET hConnect = WinHttpConnect(hSession_, wHost.c_str(), INTERNET_DEFAULT_HTTPS_PORT, 0);
    if (!hConnect) {
        std::cerr << "WinHttpConnect failed: " << GetLastError() << std::endl;
        return std::nullopt;
    }

    std::wstring wPath = toWideString(path);
    HINTERNET hRequest = WinHttpOpenRequest(
        hConnect,
        L"POST",
        wPath.c_str(),
        nullptr,
        WINHTTP_NO_REFERER,
        WINHTTP_DEFAULT_ACCEPT_TYPES,
        WINHTTP_FLAG_SECURE
    );
    if (!hRequest) {
        std::cerr << "WinHttpOpenRequest failed: " << GetLastError() << std::endl;
        WinHttpCloseHandle(hConnect);
        return std::nullopt;
    }

    // Add headers
    for (const auto& [key, value] : headers) {
        std::wstring header = toWideString(key + ": " + value);
        WinHttpAddRequestHeaders(hRequest, header.c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD);
    }

    // Send request
    BOOL result = WinHttpSendRequest(
        hRequest,
        WINHTTP_NO_ADDITIONAL_HEADERS,
        0,
        (LPVOID)jsonBody.c_str(),
        jsonBody.length(),
        jsonBody.length(),
        0
    );

    if (!result) {
        std::cerr << "WinHttpSendRequest failed: " << GetLastError() << std::endl;
        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        return std::nullopt;
    }

    result = WinHttpReceiveResponse(hRequest, nullptr);
    if (!result) {
        std::cerr << "WinHttpReceiveResponse failed: " << GetLastError() << std::endl;
        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        return std::nullopt;
    }

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

    // Read response body
    std::string responseBody;
    DWORD bytesAvailable = 0;
    do {
        bytesAvailable = 0;
        if (!WinHttpQueryDataAvailable(hRequest, &bytesAvailable)) {
            break;
        }
        if (bytesAvailable == 0) break;

        std::vector<char> buffer(bytesAvailable + 1);
        DWORD bytesRead = 0;
        if (WinHttpReadData(hRequest, buffer.data(), bytesAvailable, &bytesRead)) {
            responseBody.append(buffer.data(), bytesRead);
        }
    } while (bytesAvailable > 0);

    WinHttpCloseHandle(hRequest);
    WinHttpCloseHandle(hConnect);

    return HttpResponse{static_cast<int>(statusCode), responseBody};
#else
    return std::nullopt;
#endif
}

std::optional<HttpResponse> WinHttpClient::postMultipart(
    const std::string& host,
    const std::string& path,
    const std::map<std::string, std::string>& fields,
    const std::string& fileFieldName,
    const std::string& fileName,
    const std::string& fileContentType,
    const std::vector<char>& fileData,
    const std::map<std::string, std::string>& headers) {

#ifdef _WIN32
    if (!hSession_) {
        return std::nullopt;
    }

    std::string boundary = generateBoundary();

    // Build multipart body
    std::ostringstream body;

    // Add fields
    for (const auto& [key, value] : fields) {
        body << "--" << boundary << "\r\n";
        body << "Content-Disposition: form-data; name=\"" << key << "\"\r\n\r\n";
        body << value << "\r\n";
    }

    // Add file
    body << "--" << boundary << "\r\n";
    body << "Content-Disposition: form-data; name=\"" << fileFieldName << "\"; filename=\"" << fileName << "\"\r\n";
    body << "Content-Type: " << fileContentType << "\r\n\r\n";

    std::string bodyPrefix = body.str();
    std::string bodySuffix = "\r\n--" + boundary + "--\r\n";

    // Combine all parts
    std::vector<char> fullBody;
    fullBody.insert(fullBody.end(), bodyPrefix.begin(), bodyPrefix.end());
    fullBody.insert(fullBody.end(), fileData.begin(), fileData.end());
    fullBody.insert(fullBody.end(), bodySuffix.begin(), bodySuffix.end());

    std::wstring wHost = toWideString(host);
    HINTERNET hConnect = WinHttpConnect(hSession_, wHost.c_str(), INTERNET_DEFAULT_HTTPS_PORT, 0);
    if (!hConnect) {
        std::cerr << "WinHttpConnect failed: " << GetLastError() << std::endl;
        return std::nullopt;
    }

    std::wstring wPath = toWideString(path);
    HINTERNET hRequest = WinHttpOpenRequest(
        hConnect,
        L"POST",
        wPath.c_str(),
        nullptr,
        WINHTTP_NO_REFERER,
        WINHTTP_DEFAULT_ACCEPT_TYPES,
        WINHTTP_FLAG_SECURE
    );
    if (!hRequest) {
        std::cerr << "WinHttpOpenRequest failed: " << GetLastError() << std::endl;
        WinHttpCloseHandle(hConnect);
        return std::nullopt;
    }

    // Add headers
    for (const auto& [key, value] : headers) {
        std::wstring header = toWideString(key + ": " + value);
        WinHttpAddRequestHeaders(hRequest, header.c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD);
    }

    // Add content-type header
    std::wstring contentType = toWideString("Content-Type: multipart/form-data; boundary=" + boundary);
    WinHttpAddRequestHeaders(hRequest, contentType.c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD);

    // Send request
    BOOL result = WinHttpSendRequest(
        hRequest,
        WINHTTP_NO_ADDITIONAL_HEADERS,
        0,
        fullBody.data(),
        fullBody.size(),
        fullBody.size(),
        0
    );

    if (!result) {
        std::cerr << "WinHttpSendRequest failed: " << GetLastError() << std::endl;
        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        return std::nullopt;
    }

    result = WinHttpReceiveResponse(hRequest, nullptr);
    if (!result) {
        std::cerr << "WinHttpReceiveResponse failed: " << GetLastError() << std::endl;
        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        return std::nullopt;
    }

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

    // Read response body
    std::string responseBody;
    DWORD bytesAvailable = 0;
    do {
        bytesAvailable = 0;
        if (!WinHttpQueryDataAvailable(hRequest, &bytesAvailable)) {
            break;
        }
        if (bytesAvailable == 0) break;

        std::vector<char> buffer(bytesAvailable + 1);
        DWORD bytesRead = 0;
        if (WinHttpReadData(hRequest, buffer.data(), bytesAvailable, &bytesRead)) {
            responseBody.append(buffer.data(), bytesRead);
        }
    } while (bytesAvailable > 0);

    WinHttpCloseHandle(hRequest);
    WinHttpCloseHandle(hConnect);

    return HttpResponse{static_cast<int>(statusCode), responseBody};
#else
    return std::nullopt;
#endif
}

} // namespace secondvoice
