/*
 * SPDX-FileCopyrightText: 2026 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */
#include "catch2/utils/platform/unix/interface.h"
#include "catch2_common.h"

#include <sys/wait.h>
#include <unistd.h>

#include <cerrno>

namespace
{
using namespace TangoTest::platform::unix;

struct Child
{
    Child() { }

    Child(const Child &) = delete;
    Child(Child &&) = delete;
    Child &operator=(const Child &) = delete;
    Child &operator=(Child &&) = delete;

    int pid = -1;
    int pipefd = -1;
    BlockSigChild block_sig;

    ~Child()
    {
        if(pid != -1)
        {
            kill(pid, SIGKILL);
            int status;
            waitpid(pid, &status, WNOHANG);
        }
    }

    template <typename Fn>
    void start(Fn &&fn);
    char wait_for_byte();
};

// fork() and call the fn in the child.
//
// Fn should return a single byte to send over a pipe to the parent.
template <typename Fn>
void Child::start(Fn &&fn)
{
    int pipefds[2];
    REQUIRE(pipe(pipefds) == 0);
    pipefd = pipefds[0];

    pid_t ppid = getpid();
    pid = fork();
    REQUIRE(pid != -1);

    if(pid == 0)
    {
        close(pipefds[0]);

        kill_self_on_parent_death(ppid);

        char to_send = -1;
        try
        {
            to_send = std::forward<Fn>(fn)();
        }
        catch(Tango::DevFailed &ex)
        {
            TANGO_LOG_ERROR << "Child function threw " << ex;
        }
        catch(std::exception &ex)
        {
            TANGO_LOG_ERROR << "Child function threw " << ex.what();
        }
        catch(...)
        {
        }

        if(write(pipefds[1], &to_send, 1) == -1)
        {
            TANGO_LOG_ERROR << "Write failed " << strerror(errno);
        }
        std::exit(0);
    }

    close(pipefds[1]);
}

char Child::wait_for_byte()
{
    using std::chrono::steady_clock;

    sigset_t emptyset;
    sigemptyset(&emptyset);

    auto end = steady_clock::now() + std::chrono::seconds{3};

    while(true)
    {
        struct timespec remaining_timeout = duration_to_timespec(end - steady_clock::now());
        int ready = wait_for_fd_or_signal(pipefd, &remaining_timeout, &emptyset);

        if(ready == -1)
        {
            if(errno != EINTR)
            {
                throw_strerror("pselect()");
            }

            int status;
            int ret = waitpid(pid, &status, WNOHANG);
            pid = -1;
            if(ret == -1)
            {
                throw_strerror("waitpid()");
            }
        }
        else if(ready == 0)
        {
            throw std::runtime_error("Timeout waiting for byte from child");
        }
        else
        {
            char result;
            while(read(pipefd, &result, 1) == -1)
            {
                if(errno != EINTR)
                {
                    throw_strerror("read()");
                }
            }

            return result;
        }
    }
}

} // namespace

SCENARIO("fork()ing with ApiUtil")
{
    GIVEN("the ApiUtil singleton")
    {
        [[maybe_unused]] Tango::ApiUtil *au = Tango::ApiUtil::instance();
        CHECK(au != nullptr);

        WHEN("we fork() a child process")
        {
            Child child;
            child.start([]() -> char { return static_cast<char>(Tango::ApiUtil::is_started()); });

            THEN("the singleton does not exist in the child")
            {
                char result = child.wait_for_byte();
                REQUIRE(result == 0);
            }
        }
    }
}

SCENARIO("fork()ing with Util")
{
    GIVEN("the Util singleton")
    {
        std::vector<const char *> args = {"TestChild", "instance", "-nodb", "-ORBendPoint", "giop:tcp::0"};
        [[maybe_unused]] Tango::Util *util = Tango::Util::init(args.size(), const_cast<char **>(args.data()));
        CHECK(util != nullptr);

        WHEN("we fork() a child process")
        {
            Child child;
            child.start(
                [&args]() -> char
                {
                    try
                    {
                        Tango::Util::init(args.size(), const_cast<char **>(args.data()));
                    }
                    catch(Tango::DevFailed &ex)
                    {
                        if(std::string_view{"API_UtilSingletonPoisoned"} == ex.errors[0].reason.in())
                        {
                            return 0;
                        }
                        throw;
                    }
                    return 1;
                });

            THEN("the singleton cannot be created in the child")
            {
                char result = child.wait_for_byte();
                REQUIRE(result == 0);
            }
        }
    }
}
