// SPDX-FileCopyrightText: 2025 Contributors to Chatterino <https://chatterino.com>
//
// SPDX-License-Identifier: MIT

#include "common/websockets/WebSocketPool.hpp"

#include "mocks/BaseApplication.hpp"
#include "Test.hpp"
#include "util/OnceFlag.hpp"

using namespace chatterino;
using namespace std::chrono_literals;

namespace {

struct Listener : public WebSocketListener {
    Listener(std::vector<std::pair<bool, QByteArray>> &messages,
             OnceFlag &messageFlag, OnceFlag &closeFlag, OnceFlag &openFlag)
        : messages(messages)
        , messageFlag(messageFlag)
        , closeFlag(closeFlag)
        , openFlag(openFlag)
    {
    }

    void onClose(std::unique_ptr<WebSocketListener> /*self*/) override
    {
        this->closeFlag.set();
    }

    void onTextMessage(QByteArray data) override
    {
        this->messageFlag.set();
        messages.emplace_back(true, std::move(data));
    }

    void onBinaryMessage(QByteArray data) override
    {
        // no flag to know when the initial queue is empty
        messages.emplace_back(false, std::move(data));
    }

    void onOpen() override
    {
        this->openFlag.set();
    }

    std::vector<std::pair<bool, QByteArray>> &messages;
    OnceFlag &messageFlag;
    OnceFlag &closeFlag;
    OnceFlag &openFlag;
};

}  // namespace

TEST(WebSocketPool, tcpEcho)
{
    mock::BaseApplication app;
    WebSocketPool pool;

    std::vector<std::pair<bool, QByteArray>> messages;
    OnceFlag messageFlag;
    OnceFlag closeFlag;
    OnceFlag openFlag;

    auto handle = pool.createSocket(
        {
            .url = QUrl("ws://" + PUBSUB_WS_ADDR + "/echo?query=123&xd=wow"),
            .headers =
                {
                    {"My-Header", "my-header-VALUE"},
                    {"Another-Header", "other-header"},
                    {"Cookie", "xd"},  // "known" header
                    {"User-Agent", "MyUserAgent"},
                },
        },
        std::make_unique<Listener>(messages, messageFlag, closeFlag, openFlag));
    handle.sendBinary("message1");
    handle.sendBinary("message2");
    handle.sendBinary("message3");
    handle.sendText("message4");

    ASSERT_TRUE(messageFlag.waitFor(1s));
    ASSERT_TRUE(openFlag.isSet());
    QByteArray bigMsg(1 << 15, 'A');
    handle.sendBinary(bigMsg);
    handle.sendText("foo");
    handle.sendText("/HEADER my-header");
    handle.sendText("/HEADER another-header");
    handle.sendText("/HEADER cookie");
    handle.sendText("/HEADER user-agent");
    handle.sendText("/URL");
    handle.sendText("/CLOSE");

    ASSERT_TRUE(closeFlag.waitFor(1s));

    ASSERT_EQ(messages.size(), 11);
    ASSERT_EQ(messages[0].first, false);
    ASSERT_EQ(messages[0].second, "message1");
    ASSERT_EQ(messages[1].first, false);
    ASSERT_EQ(messages[1].second, "message2");
    ASSERT_EQ(messages[2].first, false);
    ASSERT_EQ(messages[2].second, "message3");
    ASSERT_EQ(messages[3].first, true);
    ASSERT_EQ(messages[3].second, "message4");
    ASSERT_EQ(messages[4].first, false);
    ASSERT_EQ(messages[4].second, bigMsg);
    ASSERT_EQ(messages[5].first, true);
    ASSERT_EQ(messages[5].second, "foo");
    ASSERT_EQ(messages[6].first, true);
    ASSERT_EQ(messages[6].second, "my-header-VALUE");
    ASSERT_EQ(messages[7].first, true);
    ASSERT_EQ(messages[7].second, "other-header");
    ASSERT_EQ(messages[8].first, true);
    ASSERT_EQ(messages[8].second, "xd");
    ASSERT_EQ(messages[9].first, true);
    ASSERT_EQ(messages[9].second, "MyUserAgent");
    ASSERT_EQ(messages[10].first, true);
    ASSERT_EQ(messages[10].second, "/echo?query=123&xd=wow");
}

TEST(WebSocketPool, tlsEcho)
{
    mock::BaseApplication app;
    WebSocketPool pool;

    std::vector<std::pair<bool, QByteArray>> messages;
    OnceFlag messageFlag;
    OnceFlag closeFlag;
    OnceFlag openFlag;

    auto handle = pool.createSocket(
        {
            .url = QUrl("wss://" + PUBSUB_WSS_ADDR + "/echo"),
            .headers{
                {"My-Header", "my-header-VALUE"},
                {"Another-Header", "other-header"},
                {"Cookie", "xd"},  // "known" header
            },
        },
        std::make_unique<Listener>(messages, messageFlag, closeFlag, openFlag));
    handle.sendBinary("message1");
    handle.sendBinary("message2");
    handle.sendBinary("message3");
    handle.sendText("message4");

    ASSERT_TRUE(messageFlag.waitFor(1s));
    ASSERT_TRUE(openFlag.isSet());
    QByteArray bigMsg(1 << 15, 'A');
    handle.sendBinary(bigMsg);
    handle.sendText("foo");
    handle.sendText("/HEADER my-header");
    handle.sendText("/HEADER another-header");
    handle.sendText("/HEADER cookie");
    handle.sendText("/HEADER user-agent");
    handle.sendText("/URL");
    handle.sendText("/CLOSE");

    ASSERT_TRUE(closeFlag.waitFor(1s));

    ASSERT_EQ(messages.size(), 11);
    ASSERT_EQ(messages[0].first, false);
    ASSERT_EQ(messages[0].second, "message1");
    ASSERT_EQ(messages[1].first, false);
    ASSERT_EQ(messages[1].second, "message2");
    ASSERT_EQ(messages[2].first, false);
    ASSERT_EQ(messages[2].second, "message3");
    ASSERT_EQ(messages[3].first, true);
    ASSERT_EQ(messages[3].second, "message4");
    ASSERT_EQ(messages[4].first, false);
    ASSERT_EQ(messages[4].second, bigMsg);
    ASSERT_EQ(messages[5].first, true);
    ASSERT_EQ(messages[5].second, "foo");
    ASSERT_EQ(messages[6].first, true);
    ASSERT_EQ(messages[6].second, "my-header-VALUE");
    ASSERT_EQ(messages[7].first, true);
    ASSERT_EQ(messages[7].second, "other-header");
    ASSERT_EQ(messages[8].first, true);
    ASSERT_EQ(messages[8].second, "xd");
    ASSERT_EQ(messages[9].first, true);
    ASSERT_TRUE(messages[9].second.startsWith("Chatterino"));
    ASSERT_EQ(messages[10].first, true);
    ASSERT_EQ(messages[10].second, "/echo");
}
