#include "HttpClient.h"
#include <Windows.h>
#include <winhttp.h>
#include <Core/Memory/MemoryArena.h>
#include <Core/Common/CoreUtils.h>

namespace Romeo
{
    static wchar_t* ConvertToWide(Juliet::Arena* arena, const Juliet::String& str)
    {
        Assert(arena != nullptr);
        if (str.Size == 0)
        {
            return const_cast<wchar_t*>(L"");
        }
        int wLen = MultiByteToWideChar(CP_UTF8, 0, CStr(str), static_cast<int>(str.Size), nullptr, 0);
        if (wLen <= 0)
        {
            return const_cast<wchar_t*>(L"");
        }
        wchar_t* wBuf = Juliet::ArenaPushArray<wchar_t>(arena, static_cast<size_t>(wLen + 1) JULIET_DEBUG_PARAM("WideStr"));
        MultiByteToWideChar(CP_UTF8, 0, CStr(str), static_cast<int>(str.Size), wBuf, wLen);
        wBuf[wLen] = L'\0';
        return wBuf;
    }

    bool HttpClient::Post(
        Juliet::Arena* arena,
        const Juliet::String& host,
        int port,
        const Juliet::String& path,
        bool isHttps,
        const Juliet::String& requestBody,
        Juliet::String& outResponse
    )
    {
        Assert(arena != nullptr);
        Assert(host.Size > 0);

        bool success = false;
        HINTERNET hSession = nullptr;
        HINTERNET hConnect = nullptr;
        HINTERNET hRequest = nullptr;

        hSession = WinHttpOpen(
            L"Romeo/1.0",
            WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
            nullptr,
            nullptr,
            0
        );

        if (hSession != nullptr)
        {
            WinHttpSetTimeouts(hSession, 60000, 60000, 60000, 1800000);
            wchar_t* wHost = ConvertToWide(arena, host);
            hConnect = WinHttpConnect(
                hSession,
                wHost,
                static_cast<INTERNET_PORT>(port),
                0
            );
        }

        if (hConnect != nullptr)
        {
            wchar_t* wPath = ConvertToWide(arena, path);
            hRequest = WinHttpOpenRequest(
                hConnect,
                L"POST",
                wPath,
                nullptr,
                nullptr,
                nullptr,
                isHttps ? WINHTTP_FLAG_SECURE : 0
            );
        }

        if (hRequest != nullptr)
        {
            const wchar_t* headers = L"Content-Type: application/json\r\n";
            DWORD headersLen = static_cast<DWORD>(-1);

            BOOL sendRes = WinHttpSendRequest(
                hRequest,
                headers,
                headersLen,
                const_cast<char*>(CStr(requestBody)),
                static_cast<DWORD>(requestBody.Size),
                static_cast<DWORD>(requestBody.Size),
                0
            );

            if (sendRes != 0)
            {
                BOOL recvRes = WinHttpReceiveResponse(hRequest, nullptr);
                if (recvRes != 0)
                {
                    DWORD dwSize = 0;
                    DWORD dwDownloaded = 0;
                    
                    size_t totalBytes = 0;
                    size_t capacity = 1024;
                    char* responseData = Juliet::ArenaPushArray<char>(arena, capacity JULIET_DEBUG_PARAM("HttpResponse"));

                    do
                    {
                        dwSize = 0;
                        if (WinHttpQueryDataAvailable(hRequest, &dwSize) == 0)
                        {
                            break;
                        }

                        if (dwSize == 0)
                        {
                            break;
                        }

                        if (totalBytes + dwSize >= capacity)
                        {
                            size_t newCapacity = capacity * 2;
                            if (newCapacity < totalBytes + dwSize + 1)
                            {
                                newCapacity = totalBytes + dwSize + 1;
                            }
                            
                            char* newBuf = Juliet::ArenaPushArray<char>(arena, newCapacity JULIET_DEBUG_PARAM("HttpResponseGrow"));
                            memcpy(newBuf, responseData, totalBytes);
                            responseData = newBuf;
                            capacity = newCapacity;
                        }

                        dwDownloaded = 0;
                        if (WinHttpReadData(hRequest, responseData + totalBytes, dwSize, &dwDownloaded) != 0)
                        {
                            totalBytes += dwDownloaded;
                        }
                        else
                        {
                            break;
                        }
                    } while (dwSize > 0);

                    DWORD dwStatusCode = 0;
                    DWORD dwHeaderSize = sizeof(dwStatusCode);
                    WinHttpQueryHeaders(
                        hRequest,
                        WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                        nullptr,
                        &dwStatusCode,
                        &dwHeaderSize,
                        nullptr
                    );

                    if (dwStatusCode == 200)
                    {
                        if (totalBytes > 0)
                        {
                            responseData[totalBytes] = '\0';
                            outResponse.Data = responseData;
                            outResponse.Size = totalBytes;
                            success = true;
                        }
                    }
                    else
                    {
                        if (totalBytes > 0)
                        {
                            responseData[totalBytes] = '\0';
                            printf("Romeo HttpClient: HTTP Error Status %u. Response: %s\n", static_cast<unsigned int>(dwStatusCode), responseData);
                        }
                        else
                        {
                            printf("Romeo HttpClient: HTTP Error Status %u. Empty response.\n", static_cast<unsigned int>(dwStatusCode));
                        }
                    }
                }
            }
        }

        if (hRequest != nullptr)
        {
            WinHttpCloseHandle(hRequest);
        }
        if (hConnect != nullptr)
        {
            WinHttpCloseHandle(hConnect);
        }
        if (hSession != nullptr)
        {
            WinHttpCloseHandle(hSession);
        }

        return success;
    }
}
