BareGit
#include <atomic>
#include <chrono>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>

#include <agent_smith/agent_smith.hpp>
#include <agent_smith/detail/async_queue.hpp>
#include <agent_smith/detail/gather_all.hpp>

#include <gtest/gtest.h>

namespace agent_smith
{
namespace
{

mw::Task<mw::E<int>> markAndSleep(Runtime& runtime, std::atomic<bool>& started,
                                  int value, std::chrono::milliseconds duration)
{
    started.store(true);
    auto slept = co_await runtime.sleepFor(duration);
    if(!slept)
    {
        co_return std::unexpected(slept.error());
    }

    co_return value;
}

mw::Task<mw::E<int>> awaitStarted(internal::StartedTask<int> task)
{
    co_return co_await std::move(task);
}

TEST(RuntimeTest, ConstructsSharedHttpSession)
{
    Runtime runtime;

    EXPECT_NE(&runtime.http(), nullptr);
    EXPECT_EQ(runtime.http().connectionTimeout(), std::chrono::seconds(60));
    EXPECT_EQ(runtime.http().transferTimeout(), std::chrono::seconds(60));
}

TEST(RuntimeTest, RunsPlainBlockingResult)
{
    Runtime runtime;

    auto result = syncWait(runtime.runOnWorker([]() { return 42; }));

    ASSERT_TRUE(result.has_value());
    EXPECT_EQ(*result, 42);
}

TEST(RuntimeTest, FlattensExpectedBlockingResult)
{
    Runtime runtime;

    auto result = syncWait(runtime.runOnWorker(
        []() -> mw::E<int>
        { return std::unexpected(mw::runtimeError("tool failed")); }));

    ASSERT_FALSE(result.has_value());
    EXPECT_EQ(result.error().msg(), "tool failed");
}

TEST(RuntimeTest, ConvertsBlockingExceptionsToErrors)
{
    Runtime runtime;

    auto result = syncWait(runtime.runOnWorker(
        []() -> int { throw std::runtime_error("unexpected failure"); }));

    ASSERT_FALSE(result.has_value());
    EXPECT_EQ(result.error().msg(), "unexpected failure");
}

TEST(RuntimeTest, SleepsWithoutBlockingTheCaller)
{
    Runtime runtime;
    const auto start = std::chrono::steady_clock::now();

    auto result = syncWait(runtime.sleepFor(std::chrono::milliseconds(25)));

    ASSERT_TRUE(result.has_value());
    EXPECT_GE(std::chrono::steady_clock::now() - start,
              std::chrono::milliseconds(20));
}

TEST(StartedTaskTest, StartsImmediatelyAndCanBeAwaited)
{
    Runtime runtime;
    std::atomic<bool> started = false;

    auto task = internal::StartedTask<int>::start(
        markAndSleep(runtime, started, 7, std::chrono::milliseconds(1)));

    EXPECT_TRUE(started.load());
    auto result = syncWait(awaitStarted(std::move(task)));
    ASSERT_TRUE(result.has_value());
    EXPECT_EQ(*result, 7);
}

TEST(AsyncQueueTest, DeliversItemsAcrossThreadsAndCloses)
{
    internal::AsyncQueue<int> queue;
    std::thread producer(
        [&queue]()
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(5));
            queue.push(9);
            queue.close();
        });

    auto first = syncWait(queue.pop());
    auto second = syncWait(queue.pop());
    producer.join();

    ASSERT_TRUE(first.has_value());
    EXPECT_EQ(*first, 9);
    EXPECT_FALSE(second.has_value());
}

TEST(AsyncQueueTest, DrainsBufferedItemsBeforeClosure)
{
    internal::AsyncQueue<int> queue;
    queue.push(3);
    queue.push(5);
    queue.close();

    auto first = syncWait(queue.pop());
    auto second = syncWait(queue.pop());
    auto closed = syncWait(queue.pop());

    ASSERT_TRUE(first.has_value());
    ASSERT_TRUE(second.has_value());
    EXPECT_EQ(*first, 3);
    EXPECT_EQ(*second, 5);
    EXPECT_FALSE(closed.has_value());
}

TEST(GatherAllTest, StartsAllTasksAndPreservesInputOrder)
{
    Runtime runtime;
    std::atomic<bool> first_started = false;
    std::atomic<bool> second_started = false;
    std::vector<mw::Task<mw::E<int>>> tasks;
    tasks.push_back(
        markAndSleep(runtime, first_started, 1, std::chrono::milliseconds(20)));
    tasks.push_back(
        markAndSleep(runtime, second_started, 2, std::chrono::milliseconds(1)));

    auto results = syncWait(internal::gatherAll(std::move(tasks)));

    ASSERT_EQ(results.size(), 2U);
    ASSERT_TRUE(results[0].has_value());
    ASSERT_TRUE(results[1].has_value());
    EXPECT_TRUE(first_started.load());
    EXPECT_TRUE(second_started.load());
    EXPECT_EQ(*results[0], 1);
    EXPECT_EQ(*results[1], 2);
}

} // namespace
} // namespace agent_smith