#include "AiInputPort.h"
#include "AiFrameConverter.h"
#include <objbase.h>
#include <utility>

AiInputPort::AiInputPort()
    : running_(false), stopping_(false), lastAcceptTickMs_(0)
{
}

AiInputPort::~AiInputPort()
{
    Stop();
}

void AiInputPort::SetResultCallback(ResultCallback cb)
{
    std::lock_guard<std::mutex> guard(lock_);
    resultCb_ = cb;
}

void AiInputPort::Start(const AiInputConfig& cfg)
{
    Stop();
    {
        std::lock_guard<std::mutex> guard(lock_);
        config_ = cfg;
        if (config_.targetFps <= 0) config_.targetFps = 1;
        if (config_.jpegQuality <= 0) config_.jpegQuality = 85;
        queue_.clear();
        lastAcceptTickMs_ = 0;
        stopping_.store(false);
        running_.store(true);
    }
    worker_ = std::thread(&AiInputPort::WorkerThread, this);
}

void AiInputPort::Stop()
{
    if (!running_.load() && !worker_.joinable()) return;
    stopping_.store(true);
    running_.store(false);
    cv_.notify_all();
    if (worker_.joinable()) worker_.join();
    std::lock_guard<std::mutex> guard(lock_);
    queue_.clear();
}

bool AiInputPort::IsRunning() const
{
    return running_.load();
}

bool AiInputPort::ShouldAcceptFrame(unsigned long long nowMs)
{
    int fps = config_.targetFps;
    if (fps <= 0) fps = 1;
    unsigned long long interval = 1000ULL / (unsigned long long)fps;
    if (interval < 1) interval = 1;
    if (lastAcceptTickMs_ == 0 || nowMs - lastAcceptTickMs_ >= interval) {
        lastAcceptTickMs_ = nowMs;
        return true;
    }
    return false;
}

void AiInputPort::PushFrame(const HWS_VIDEO_FRAME* frame)
{
    if (!frame || !running_.load()) return;

    AiCapturedFrame copied;
    {
        std::lock_guard<std::mutex> guard(lock_);
        if (!running_.load()) return;
        if (!ShouldAcceptFrame(GetTickCount64())) return;
        if (!AiCopySdkFrame(frame, config_.slot, &copied)) return;

        // AI analysis is not the real-time video path. Keep only the newest frame.
        queue_.clear();
        queue_.push_back(std::move(copied));
    }
    cv_.notify_one();
}

void AiInputPort::WorkerThread()
{
    HRESULT cohr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    bool needCoUninit = SUCCEEDED(cohr);
    AiHttpClient httpClient;
    AiWebSocketClient wsClient;

    for (;;) {
        AiCapturedFrame frame;
        AiInputConfig cfg;
        ResultCallback cb;

        {
            std::unique_lock<std::mutex> guard(lock_);
            cv_.wait(guard, [this] { return stopping_.load() || !queue_.empty(); });
            if (stopping_.load() && queue_.empty()) break;
            frame = std::move(queue_.front());
            queue_.pop_front();
            cfg = config_;
            cb = resultCb_;
        }

        AiDemoResult result;
        result.slot = frame.slot;
        result.timestamp100ns = frame.timestamp100ns;
        result.frameIndex = frame.frameIndex;
        result.statusUtf8 = "error";

        std::vector<unsigned char> jpeg;
        std::wstring errorText;
        if (!AiEncodeFrameToJpeg(frame, cfg.jpegQuality, &jpeg, &errorText)) {
            result.ok = false;
            result.summaryUtf8 = AiWideToUtf8(errorText.empty() ? L"AI JPEG encode failed." : errorText);
            if (cb) cb(result);
            continue;
        }

        std::string response;
        bool requestOk = false;

        if (cfg.transport == AI_TRANSPORT_WEBSOCKET) {
            if (!wsClient.IsConnected() || _wcsicmp(wsClient.Endpoint().c_str(), cfg.endpoint.c_str()) != 0) {
                if (!wsClient.Connect(cfg.endpoint, &errorText)) {
                    result.ok = false;
                    result.summaryUtf8 = AiWideToUtf8(errorText.empty() ? L"AI WebSocket connect failed." : errorText);
                    if (cb) cb(result);
                    continue;
                }
            }

            requestOk = wsClient.SendAnalyzeFrame(cfg.slot,
                                                  frame.timestamp100ns,
                                                  frame.frameIndex,
                                                  frame.width,
                                                  frame.height,
                                                  jpeg,
                                                  cfg.modeUtf8,
                                                  cfg.promptUtf8,
                                                  &response,
                                                  &errorText);
            if (!requestOk) {
                result.ok = false;
                result.summaryUtf8 = AiWideToUtf8(errorText.empty() ? L"AI WebSocket request failed." : errorText);
                if (cb) cb(result);
                continue;
            }
        } else {
            requestOk = httpClient.PostAnalyzeFrame(cfg.endpoint, cfg.slot, frame.timestamp100ns, frame.frameIndex,
                                                    jpeg, cfg.modeUtf8, cfg.promptUtf8, &response, &errorText);
            if (!requestOk) {
                result.ok = false;
                result.summaryUtf8 = AiWideToUtf8(errorText.empty() ? L"AI HTTP request failed." : errorText);
                if (cb) cb(result);
                continue;
            }
        }

        result.ok = true;
        result.rawJsonUtf8 = response;
        result.typeUtf8 = AiExtractJsonString(response, "type");
        if (result.typeUtf8.empty()) result.typeUtf8 = "video.detections";
        result.summaryUtf8 = AiExtractJsonString(response, "summary");
        result.statusUtf8 = AiExtractJsonString(response, "status");
        result.detections = AiExtractDetections(response);
        result.events = AiExtractEvents(response);
        if (result.summaryUtf8.empty()) result.summaryUtf8 = "AI service returned result.";
        if (result.statusUtf8.empty()) result.statusUtf8 = "ok";
        if (cb) cb(result);
    }
    wsClient.Close();
    if (needCoUninit) CoUninitialize();
}
