#include "config.h"

#include "log_manager.hpp"
#include "paths.hpp"

#include <phosphor-logging/commit.hpp>
#include <sdbusplus/async.hpp>
#include <sdbusplus/server/manager.hpp>
#include <xyz/openbmc_project/Logging/Entry/client.hpp>
#include <xyz/openbmc_project/Logging/event.hpp>

#include <thread>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

namespace phosphor::logging::test
{
using LoggingCleared = sdbusplus::event::xyz::openbmc_project::Logging::Cleared;
using LoggingEntry = sdbusplus::client::xyz::openbmc_project::logging::Entry<>;

// Fixture to spawn the log-manager for dbus-based testing.
class TestLogManagerDbus : public ::testing::Test
{
  protected:
    // Create the daemon and sdbusplus::async::contexts.
    void SetUp() override
    {
        // The daemon requires directories to be created first.
        std::filesystem::create_directories(phosphor::logging::paths::error());

        data = std::make_unique<fixture_data>();
    }

    // Stop the daemon, etc.
    void TearDown() override
    {
        data.reset();
    }

    /** Run a client task, wait for it to complete, and stop daemon. */
    template <typename T>
    void run(T&& t)
    {
        data->client_ctx.spawn(std::move(t) | stdexec::then([this]() {
                                   data->stop(data->client_ctx);
                               }));
        data->client_ctx.run();
    }

    // Data for the fixture.
    struct fixture_data
    {
        fixture_data() :
            client_ctx(), server_ctx(), objManager(server_ctx, OBJ_LOGGING),
            iMgr(server_ctx, OBJ_INTERNAL), mgr(server_ctx, OBJ_LOGGING, iMgr)
        {
            // Create a thread for the daemon.
            task = std::thread([this]() {
                server_ctx.request_name(BUSNAME_LOGGING);
                server_ctx.run();
            });
        }

        ~fixture_data()
        {
            // Stop the server and wait for the thread to exit.
            stop(server_ctx);
            task.join();
        }

        // Spawn a task to gracefully shutdown an sdbusplus::async::context
        static void stop(sdbusplus::async::context& ctx)
        {
            ctx.spawn(stdexec::just() |
                      stdexec::then([&ctx]() { ctx.request_stop(); }));
        }

        sdbusplus::async::context client_ctx;
        sdbusplus::async::context server_ctx;
        sdbusplus::server::manager_t objManager;
        internal::Manager iMgr;
        Manager mgr;
        std::thread task;
    };

    std::unique_ptr<fixture_data> data;
};

// Ensure we can successfully create and throw an sdbusplus event.
TEST_F(TestLogManagerDbus, GenerateSimpleEvent)
{
    EXPECT_THROW(
        { throw LoggingCleared("NUMBER_OF_LOGS", 1); }, LoggingCleared);
    return;
}

// Call the synchronous version of the commit function and verify that the
// daemon gives us a path.
TEST_F(TestLogManagerDbus, CallCommitSync)
{
    auto path = lg2::commit(LoggingCleared("NUMBER_OF_LOGS", 3));
    ASSERT_FALSE(path.str.empty());
    EXPECT_THAT(path.str,
                ::testing::StartsWith(
                    std::filesystem::path(LoggingEntry::namespace_path::value) /
                    LoggingEntry::namespace_path::entry));
}

// Call the asynchronous version of the commit function and verify that the
// metadata is saved correctly.
TEST_F(TestLogManagerDbus, CallCommitAsync)
{
    sdbusplus::message::object_path path{};
    std::string log_count{};

    auto create_log = [this, &path, &log_count]() -> sdbusplus::async::task<> {
        // Log an event.
        path = co_await lg2::commit(data->client_ctx,
                                    LoggingCleared("NUMBER_OF_LOGS", 6));

        // Grab the additional data.
        auto additionalData = co_await LoggingEntry(data->client_ctx)
                                  .service(Entry::default_service)
                                  .path(path.str)
                                  .additional_data();

        // Extract the NUMBER_OF_LOGS.
        for (const auto& value : additionalData)
        {
            if (value.starts_with("NUMBER_OF_LOGS="))
            {
                log_count = value.substr(value.find_first_of('=') + 1);
            }
        }

        co_return;
    };

    run(create_log());

    ASSERT_FALSE(path.str.empty());
    ASSERT_FALSE(log_count.empty());

    EXPECT_THAT(path.str,
                ::testing::StartsWith(
                    std::filesystem::path(LoggingEntry::namespace_path::value) /
                    LoggingEntry::namespace_path::entry));

    EXPECT_EQ(log_count, "6");
}

} // namespace phosphor::logging::test