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

#include "common/websockets/detail/WebSocketPoolImpl.hpp"

#include "Application.hpp"
#include "common/QLogging.hpp"
#include "common/websockets/detail/WebSocketConnection.hpp"
#include "util/RenameThread.hpp"

#include <boost/certify/https_verification.hpp>
#include <QStringBuilder>

namespace chatterino::ws::detail {

WebSocketPoolImpl::WebSocketPoolImpl(const QString &shortName)
    : ioc(1)
    , ssl(boost::asio::ssl::context::tls_client)
    , work(this->ioc.get_executor())
{
    boost::system::error_code ec;
    auto _ = this->ssl.set_options(
        boost::asio::ssl::context::no_tlsv1 |
            boost::asio::ssl::context::no_tlsv1_1 |
            boost::asio::ssl::context::default_workarounds |
            boost::asio::ssl::context::single_dh_use,
        ec);
    if (ec)
    {
        qCWarning(chatterinoWebsocket) << "Failed to set SSL context options"
                                       << QString::fromStdString(ec.message());
    }

#ifdef CHATTERINO_WITH_TESTS
    if (!getApp()->isTest())
#endif
    {
        this->ssl.set_verify_mode(
            boost::asio::ssl::verify_peer |
            boost::asio::ssl::verify_fail_if_no_peer_cert);
        this->ssl.set_default_verify_paths();

        boost::certify::enable_native_https_server_verification(this->ssl);
    }

    this->ioThread = std::make_unique<std::thread>([this] {
        this->ioc.run();
        this->shutdownFlag.set();
    });

    auto threadName = [&]() -> QString {
        if (shortName.isEmpty())
        {
            return "WebSocketPool";
        }
        return "WS-" % shortName;
    }();
    renameThread(*this->ioThread, threadName);
}

WebSocketPoolImpl::~WebSocketPoolImpl()
{
    assert(this->closing);
    // After 10s, we stop and, through std::thread::~thread, std::terminate will
    // be called.
    this->tryShutdown(std::chrono::seconds{10});
}

bool WebSocketPoolImpl::tryShutdown(std::chrono::milliseconds timeout)
{
    this->closing = true;
    if (!this->ioThread || !this->ioThread->joinable())
    {
        return true;
    }

    this->work.reset();
    {
        std::lock_guard g(this->connectionMutex);
        for (const auto &conn : this->connections)
        {
            conn->close();
        }
    }

    if (!this->shutdownFlag.waitFor(timeout))
    {
        qCWarning(chatterinoWebsocket)
            << "Failed to gracefully close all connections in time";
        return false;
    }

    if (this->ioThread->joinable())
    {
        this->ioThread->join();
    }
    return true;
}

void WebSocketPoolImpl::removeConnection(WebSocketConnection *conn)
{
    std::lock_guard g(this->connectionMutex);
    std::erase_if(this->connections, [conn](const auto &v) {
        return v.get() == conn;
    });
}

}  // namespace chatterino::ws::detail
