/*
 * SPDX-FileCopyrightText: 2025 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */
#include "catch2_common.h"

#include <thread>

class DeviceServerCleanup : public TANGO_BASE_CLASS
{
  public:
    DeviceServerCleanup(Tango::DeviceClass *device_class, const char *name) :
        TANGO_BASE_CLASS(device_class, name)
    {
    }

    ~DeviceServerCleanup() override { }

    void init_device() override
    {
        attr_value = 0.0;
    }

    void push_change()
    {
        TANGO_LOG_DEBUG << "Pushing CHANGE_EVENT with value " << attr_value << " to \"attr\"";
        this->push_change_event("attr", &attr_value);
    }

    void read_attribute(Tango::Attribute &att)
    {
        att.set_value(&attr_value);
    }

    static void attribute_factory(std::vector<Tango::Attr *> &attrs)
    {
        auto *attr = new TangoTest::AutoAttr<&DeviceServerCleanup::read_attribute>("attr", Tango::DEV_DOUBLE);
        attr->set_change_event(true, false);
        attrs.push_back(attr);
    }

    static void command_factory(std::vector<Tango::Command *> &cmds)
    {
        cmds.push_back(new TangoTest::AutoCommand<&DeviceServerCleanup::push_change>("PushChange"));
    }

  private:
    Tango::DevDouble attr_value = 0.0;
};

TANGO_TEST_AUTO_DEV_CLASS_INSTANTIATE(DeviceServerCleanup, DeviceServerCleanup)

struct TangoLoopThread
{
    explicit TangoLoopThread(std::string name, bool with_filedb = false) :
        m_name{std::move(name)}
    {
        std::vector<const char *> argv{"ThreadServer", m_name.c_str(), "-ORBendPoint", "giop:tcp::54321"};
        std::string buffer;

        if(with_filedb)
        {
            m_filedb_path = TangoTest::get_next_file_database_location();
            TANGO_LOG_INFO << "Creating filedb " << *m_filedb_path;
            TangoTest::write_filedb(*m_filedb_path, "ThreadServer", m_name, "DeviceServerCleanup", "foo/bar/1");
            buffer = std::string{"-file="} + *m_filedb_path;
            argv.push_back(buffer.c_str());
        }
        else
        {
            argv.push_back("-nodb");
            argv.push_back("-dlist");
            argv.push_back("DeviceServerCleanup::foo/bar/1");
        }

        auto argc = static_cast<int>(argv.size());

        TangoTest::Event start_event;
        m_thread = std::thread{[&]()
                               {
                                   omni_thread::ensure_self se;
                                   TangoTest::EnableClasses ec{{"DeviceServerCleanup"}};
                                   auto *tg = Tango::Util::init(argc, (char **) argv.data());
                                   tg->register_dserver_constructor(TangoTest::detail::constructor);
                                   tg->server_init();
                                   start_event.set();
                                   tg->server_run();
                                   tg->server_cleanup();
                                   Tango::Util::cleanup();
                               }};
        start_event.wait();
    }

    std::unique_ptr<Tango::DeviceProxy> get_device()
    {
        return std::make_unique<Tango::DeviceProxy>("tango://localhost:54321/foo/bar/1#dbase=no");
    }

    ~TangoLoopThread()
    {
        std::stringstream ss;
        ss << "dserver/ThreadServer/" << m_name;
        std::string fqtrl = TangoTest::make_nodb_fqtrl(54321, ss.str());
        try
        {
            auto dserver = std::make_unique<Tango::DeviceProxy>(fqtrl);
            dserver->command_inout("kill");
        }
        catch(...)
        {
            TANGO_LOG_ERROR << "Failed to send kill command to Tango loop thread.";
            m_thread.detach();
        }

        if(m_thread.joinable())
        {
            m_thread.join();
        }

        if(m_filedb_path.has_value())
        {
            std::remove(m_filedb_path->c_str());
        }
    }

    std::thread m_thread;
    std::string m_name;
    std::optional<std::string> m_filedb_path;
};

SCENARIO("Starting Tango twice", "[skipwin32]")
{
    bool with_filedb = GENERATE(true, false);

    GIVEN("Tango has run with events" << (with_filedb ? " and filedb " : " ")
                                      << "and stopped on a separate thread already")
    {
        using namespace TangoTest::Matchers;

        TangoTest::CallbackMock<Tango::EventData> callback;

        {
            TangoLoopThread tlt{"1", with_filedb};

            std::shared_ptr<Tango::DeviceProxy> device = tlt.get_device();
            TangoTest::Subscription s{device, "attr", Tango::CHANGE_EVENT, &callback};

            require_initial_events(callback, 0.0);

            device->command_inout("PushChange");

            auto maybe_event = callback.pop_next_event();
            REQUIRE(maybe_event != std::nullopt);
            REQUIRE_THAT(maybe_event, EventValueMatches(AnyLikeContains(0.0)));
        }

        WHEN("we go to start the Tango loop again")
        {
            TangoLoopThread tlt{"2", with_filedb};

            THEN("we can read the state of the Tango device")
            {
                std::unique_ptr<Tango::DeviceProxy> device = tlt.get_device();
                Tango::DeviceAttribute da = device->read_attribute("state");
                REQUIRE_THAT(da, AnyLikeContains(Tango::UNKNOWN));
            }

            THEN("we can receive events from the Tango device")
            {
                std::shared_ptr<Tango::DeviceProxy> device = tlt.get_device();
                TangoTest::Subscription s{device, "attr", Tango::CHANGE_EVENT, &callback};

                require_initial_events(callback, 0.0);

                device->command_inout("PushChange");

                auto maybe_event = callback.pop_next_event();
                REQUIRE(maybe_event != std::nullopt);
                REQUIRE_THAT(maybe_event, EventValueMatches(AnyLikeContains(0.0)));
            }
        }
    }
}
