#include "AiHttpClient.h"
#include <winhttp.h>
#include <sstream>
#include <iomanip>
#include <cstdlib>

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

static std::string AiEscapeJsonAscii(const std::string& s)
{
    std::string out;
    out.reserve(s.size() + 16);
    for (char c : s) {
        switch (c) {
        case '\\': out += "\\\\"; break;
        case '"': out += "\\\""; break;
        case '\n': out += "\\n"; break;
        case '\r': out += "\\r"; break;
        case '\t': out += "\\t"; break;
        default: out += c; break;
        }
    }
    return out;
}

static void AiAppendString(std::vector<unsigned char>& body, const std::string& text)
{
    body.insert(body.end(), text.begin(), text.end());
}

static bool AiBuildMultipartBody(int slot,
                                 long long timestamp100ns,
                                 long long frameIndex,
                                 const std::vector<unsigned char>& jpeg,
                                 const std::string& modeUtf8,
                                 const std::string& promptUtf8,
                                 const std::string& boundary,
                                 std::vector<unsigned char>* body)
{
    if (!body || jpeg.empty()) return false;
    body->clear();

    auto addField = [&](const char* name, const std::string& value) {
        AiAppendString(*body, "--" + boundary + "\r\n");
        AiAppendString(*body, std::string("Content-Disposition: form-data; name=\"") + name + "\"\r\n\r\n");
        AiAppendString(*body, value + "\r\n");
    };

    addField("slot", std::to_string(slot));
    addField("timestamp100ns", std::to_string(timestamp100ns));
    addField("frameIndex", std::to_string(frameIndex));
    addField("mode", modeUtf8);
    addField("prompt", promptUtf8);

    AiAppendString(*body, "--" + boundary + "\r\n");
    AiAppendString(*body, "Content-Disposition: form-data; name=\"image\"; filename=\"frame.jpg\"\r\n");
    AiAppendString(*body, "Content-Type: image/jpeg\r\n\r\n");
    body->insert(body->end(), jpeg.begin(), jpeg.end());
    AiAppendString(*body, "\r\n--" + boundary + "--\r\n");
    return true;
}

static bool AiCrackUrl(const std::wstring& endpoint, URL_COMPONENTS* comp,
                       std::wstring* host, std::wstring* path, INTERNET_PORT* port, bool* secure)
{
    if (!comp || !host || !path || !port || !secure) return false;
    memset(comp, 0, sizeof(*comp));
    wchar_t hostBuf[256] = {};
    wchar_t pathBuf[2048] = {};
    comp->dwStructSize = sizeof(*comp);
    comp->lpszHostName = hostBuf;
    comp->dwHostNameLength = _countof(hostBuf);
    comp->lpszUrlPath = pathBuf;
    comp->dwUrlPathLength = _countof(pathBuf);
    if (!WinHttpCrackUrl(endpoint.c_str(), 0, 0, comp)) return false;
    *host = std::wstring(comp->lpszHostName, comp->dwHostNameLength);
    *path = std::wstring(comp->lpszUrlPath, comp->dwUrlPathLength);
    *port = comp->nPort;
    *secure = (comp->nScheme == INTERNET_SCHEME_HTTPS);
    return !host->empty() && !path->empty();
}

bool AiHttpClient::PostAnalyzeFrame(const std::wstring& endpoint,
                                    int slot,
                                    long long timestamp100ns,
                                    long long frameIndex,
                                    const std::vector<unsigned char>& jpeg,
                                    const std::string& modeUtf8,
                                    const std::string& promptUtf8,
                                    std::string* responseUtf8,
                                    std::wstring* errorText)
{
    if (responseUtf8) responseUtf8->clear();
    if (jpeg.empty()) { if (errorText) *errorText = L"No JPEG data."; return false; }

    URL_COMPONENTS comp = {};
    std::wstring host, path;
    INTERNET_PORT port = 0;
    bool secure = false;
    if (!AiCrackUrl(endpoint, &comp, &host, &path, &port, &secure)) {
        if (errorText) *errorText = L"Invalid AI service URL.";
        return false;
    }

    const std::string boundary = "----hws-ai-demo-boundary-20260620";
    std::vector<unsigned char> body;
    if (!AiBuildMultipartBody(slot, timestamp100ns, frameIndex, jpeg, modeUtf8, promptUtf8, boundary, &body)) {
        if (errorText) *errorText = L"Build HTTP body failed.";
        return false;
    }

    HINTERNET session = NULL;
    HINTERNET connect = NULL;
    HINTERNET request = NULL;
    bool ok = false;

    do {
        session = WinHttpOpen(L"HWS AI Video Inspector Demo/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                              WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
        if (!session) { if (errorText) *errorText = L"WinHttpOpen failed."; break; }
        WinHttpSetTimeouts(session, 3000, 3000, 5000, 15000);

        connect = WinHttpConnect(session, host.c_str(), port, 0);
        if (!connect) { if (errorText) *errorText = L"WinHttpConnect failed."; break; }

        DWORD flags = secure ? WINHTTP_FLAG_SECURE : 0;
        request = WinHttpOpenRequest(connect, L"POST", path.c_str(), NULL,
                                     WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
        if (!request) { if (errorText) *errorText = L"WinHttpOpenRequest failed."; break; }

        std::wstring headers = L"Content-Type: multipart/form-data; boundary=" + AiUtf8ToWide(boundary) + L"\r\n";
        BOOL sent = WinHttpSendRequest(request,
                                       headers.c_str(), (DWORD)-1L,
                                       body.data(), (DWORD)body.size(),
                                       (DWORD)body.size(), 0);
        if (!sent) { if (errorText) *errorText = L"WinHttpSendRequest failed."; break; }
        if (!WinHttpReceiveResponse(request, NULL)) { if (errorText) *errorText = L"WinHttpReceiveResponse failed."; break; }

        DWORD status = 0;
        DWORD statusSize = sizeof(status);
        WinHttpQueryHeaders(request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                            WINHTTP_HEADER_NAME_BY_INDEX, &status, &statusSize, WINHTTP_NO_HEADER_INDEX);
        if (status < 200 || status >= 300) {
            std::wstringstream ss;
            ss << L"AI service HTTP status=" << status;
            if (errorText) *errorText = ss.str();
            break;
        }

        std::string resp;
        for (;;) {
            DWORD available = 0;
            if (!WinHttpQueryDataAvailable(request, &available)) break;
            if (available == 0) break;
            std::string chunk;
            chunk.resize(available);
            DWORD read = 0;
            if (!WinHttpReadData(request, &chunk[0], available, &read)) break;
            chunk.resize(read);
            resp += chunk;
        }
        if (responseUtf8) *responseUtf8 = resp;
        ok = true;
    } while (false);

    if (request) WinHttpCloseHandle(request);
    if (connect) WinHttpCloseHandle(connect);
    if (session) WinHttpCloseHandle(session);
    return ok;
}

std::string AiExtractJsonString(const std::string& json, const char* key)
{
    if (!key || !key[0]) return std::string();
    std::string pattern = std::string("\"") + key + "\"";
    size_t pos = json.find(pattern);
    if (pos == std::string::npos) return std::string();
    pos = json.find(':', pos + pattern.size());
    if (pos == std::string::npos) return std::string();
    ++pos;
    while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t' || json[pos] == '\r' || json[pos] == '\n')) ++pos;
    if (pos >= json.size() || json[pos] != '"') return std::string();
    ++pos;
    std::string out;
    for (; pos < json.size(); ++pos) {
        char c = json[pos];
        if (c == '"') break;
        if (c == '\\' && pos + 1 < json.size()) {
            char e = json[++pos];
            switch (e) {
            case 'n': out += '\n'; break;
            case 'r': out += '\r'; break;
            case 't': out += '\t'; break;
            case '"': out += '"'; break;
            case '\\': out += '\\'; break;
            default: out += e; break;
            }
        } else {
            out += c;
        }
    }
    return out;
}


static size_t AiFindMatchingBracket(const std::string& s, size_t openPos, char openCh, char closeCh)
{
    bool inString = false;
    bool escape = false;
    int depth = 0;
    for (size_t i = openPos; i < s.size(); ++i) {
        char c = s[i];
        if (escape) { escape = false; continue; }
        if (c == '\\') { escape = inString; continue; }
        if (c == '"') { inString = !inString; continue; }
        if (inString) continue;
        if (c == openCh) ++depth;
        else if (c == closeCh) {
            --depth;
            if (depth == 0) return i;
        }
    }
    return std::string::npos;
}

static std::string AiExtractJsonObject(const std::string& json, const char* key)
{
    if (!key || !key[0]) return std::string();
    std::string pattern = std::string("\"") + key + "\"";
    size_t pos = json.find(pattern);
    if (pos == std::string::npos) return std::string();
    pos = json.find(':', pos + pattern.size());
    if (pos == std::string::npos) return std::string();
    pos = json.find('[', pos + 1);
    if (pos == std::string::npos) return std::string();
    size_t end = AiFindMatchingBracket(json, pos, '[', ']');
    if (end == std::string::npos || end <= pos) return std::string();
    return json.substr(pos + 1, end - pos - 1);
}

static bool AiExtractJsonNumber(const std::string& obj, const char* key, double* out)
{
    if (!out || !key || !key[0]) return false;
    std::string pattern = std::string("\"") + key + "\"";
    size_t pos = obj.find(pattern);
    if (pos == std::string::npos) return false;
    pos = obj.find(':', pos + pattern.size());
    if (pos == std::string::npos) return false;
    ++pos;
    while (pos < obj.size() && (obj[pos] == ' ' || obj[pos] == '\t' || obj[pos] == '\r' || obj[pos] == '\n')) ++pos;
    char* endPtr = NULL;
    double value = std::strtod(obj.c_str() + pos, &endPtr);
    if (endPtr == obj.c_str() + pos) return false;
    *out = value;
    return true;
}

std::vector<AiDetection> AiExtractDetections(const std::string& json)
{
    std::vector<AiDetection> out;
    std::string arrayText = AiExtractJsonObject(json, "detections");
    if (arrayText.empty()) return out;

    size_t pos = 0;
    while (pos < arrayText.size() && out.size() < 64) {
        size_t objStart = arrayText.find('{', pos);
        if (objStart == std::string::npos) break;
        size_t objEnd = AiFindMatchingBracket(arrayText, objStart, '{', '}');
        if (objEnd == std::string::npos || objEnd <= objStart) break;
        std::string obj = arrayText.substr(objStart, objEnd - objStart + 1);

        AiDetection d;
        double v = 0.0;
        if (AiExtractJsonNumber(obj, "class_id", &v)) d.classId = (int)v;
        d.labelUtf8 = AiExtractJsonString(obj, "label");
        if (AiExtractJsonNumber(obj, "confidence", &v)) d.confidence = v;
        if (AiExtractJsonNumber(obj, "x", &v)) d.x = v;
        if (AiExtractJsonNumber(obj, "y", &v)) d.y = v;
        if (AiExtractJsonNumber(obj, "w", &v)) d.w = v;
        if (AiExtractJsonNumber(obj, "h", &v)) d.h = v;

        if (d.w > 1.0 && d.h > 1.0) out.push_back(d);
        pos = objEnd + 1;
    }
    return out;
}

std::vector<AiEvent> AiExtractEvents(const std::string& json)
{
    std::vector<AiEvent> out;
    std::string arrayText = AiExtractJsonObject(json, "events");
    if (arrayText.empty()) return out;

    size_t pos = 0;
    while (pos < arrayText.size() && out.size() < 64) {
        size_t objStart = arrayText.find('{', pos);
        if (objStart == std::string::npos) break;
        size_t objEnd = AiFindMatchingBracket(arrayText, objStart, '{', '}');
        if (objEnd == std::string::npos || objEnd <= objStart) break;
        std::string obj = arrayText.substr(objStart, objEnd - objStart + 1);

        AiEvent e;
        e.eventIdUtf8 = AiExtractJsonString(obj, "event_id");
        e.typeUtf8 = AiExtractJsonString(obj, "type");
        e.levelUtf8 = AiExtractJsonString(obj, "level");
        e.labelUtf8 = AiExtractJsonString(obj, "label");
        e.messageUtf8 = AiExtractJsonString(obj, "message");

        double v = 0.0;
        if (AiExtractJsonNumber(obj, "prev_count", &v)) e.prevCount = (int)v;
        if (AiExtractJsonNumber(obj, "current_count", &v)) e.currentCount = (int)v;

        if (!e.typeUtf8.empty() || !e.messageUtf8.empty()) out.push_back(e);
        pos = objEnd + 1;
    }
    return out;
}
