/*
 * Copyright 2009- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#include <cstdlib>
#include <iostream>

#include <boost/test/unit_test.hpp>

#include "ecflow/core/EcfPortLock.hpp"
#include "ecflow/core/Host.hpp"
#include "ecflow/core/Log.hpp"
#include "ecflow/node/Defs.hpp"
#include "ecflow/server/Server.hpp"
#include "ecflow/server/ServerEnvironment.hpp"

using namespace std;
using namespace ecf;

BOOST_AUTO_TEST_SUITE(U_Server)

BOOST_AUTO_TEST_SUITE(T_Server)

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// make public the function we wish to test:
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class TestServer : public Server {
public:
    explicit TestServer(boost::asio::io_context& io, ServerEnvironment& s) : Server(io, s) {}
    ~TestServer() override = default;

    // abort server if check pt files exist, but can't be loaded
    //   bool load_check_pt_file_on_startup();
    //   void loadCheckPtFile();
    //   bool restore_from_checkpt(const std::string& filename, bool& failed);
    //   void set_server_state(SState::State);

    /// AbstractServer functions
    SState::State state() const override { return Server::state(); }
    std::pair<std::string, std::string> hostPort() const override { return Server::hostPort(); }
    defs_ptr defs() const override { return Server::defs(); }
    // virtual void updateDefs(defs_ptr,bool force);
    void clear_defs() override { Server::clear_defs(); }
    //   virtual void checkPtDefs(ecf::CheckPt::Mode m = ecf::CheckPt::UNDEFINED,
    //                               int check_pt_interval = 0,
    //                               int check_pt_save_time_alarm = 0);
    void restore_defs_from_checkpt() override { Server::restore_defs_from_checkpt(); }
    void nodeTreeStateChanged() override { Server::nodeTreeStateChanged(); }
    bool allowTaskCommunication() const override { return Server::allowTaskCommunication(); }
    void shutdown() override { Server::shutdown(); }
    void halted() override { Server::halted(); }
    void restart() override { Server::restart(); }

    bool reloadWhiteListFile(std::string& errorMsg) override { return Server::reloadWhiteListFile(errorMsg); }
    bool reloadPasswdFile(std::string& errorMsg) override { return Server::reloadPasswdFile(errorMsg); }

    bool authenticateReadAccess(const std::string& user, bool custom_user, const std::string& passwd) override {
        return Server::authenticateReadAccess(user, custom_user, passwd);
    }
    bool authenticateReadAccess(const std::string& user,
                                bool custom_user,
                                const std::string& passwd,
                                const std::string& path) override {
        return Server::authenticateReadAccess(user, custom_user, passwd, path);
    }
    bool authenticateReadAccess(const std::string& user,
                                bool custom_user,
                                const std::string& passwd,
                                const std::vector<std::string>& paths) override {
        return Server::authenticateReadAccess(user, custom_user, passwd, paths);
    }

    bool authenticateWriteAccess(const std::string& user) override { return Server::authenticateWriteAccess(user); }
    bool authenticateWriteAccess(const std::string& user, const std::string& path) override {
        return Server::authenticateWriteAccess(user, path);
    }
    bool authenticateWriteAccess(const std::string& user, const std::vector<std::string>& paths) override {
        return Server::authenticateWriteAccess(user, paths);
    }

    bool lock(const std::string& user) override { return Server::lock(user); }
    void unlock() override { Server::unlock(); }
    const std::string& lockedUser() const override { return Server::lockedUser(); }
    //   virtual void traverse_node_tree_and_job_generate(const boost::posix_time::ptime& time_now, bool
    //   user_cmd_context) const;
    int poll_interval() const override { return Server::poll_interval(); }
    //   virtual void debug_server_on();
    //   virtual void debug_server_off();
    //   virtual bool debug() const;
};

void test_the_server(const std::string& port) {
    std::string server_port = "--port=" + port;
    int argc                = 3;
    char* argv[]            = {const_cast<char*>("ServerEnvironment"),
                               const_cast<char*>(server_port.c_str()),
                               const_cast<char*>("--ecfinterval=12")};

    ServerEnvironment server_environment(argc, argv); // This can throw ServerEnvironmentException
    std::string errorMsg;
    BOOST_CHECK_MESSAGE(server_environment.valid(errorMsg), errorMsg);

    {
        boost::asio::io_context io;
        TestServer theServer(io, server_environment); // This can throw exception, bind address in use.

        BOOST_REQUIRE_MESSAGE(theServer.defs(), "Expected defs to be created");

        const std::vector<Variable>& server_variables = theServer.defs()->server().server_variables();
        BOOST_REQUIRE_MESSAGE(!server_variables.empty(), "Expected defs to be updated with the server variables");

        // for(size_t i = 0; i < server_variables.size(); ++i)  cout << server_variables[i].dump() << "\n";
        const std::string& ecf_port = theServer.defs()->server().find_variable("ECF_PORT");
        BOOST_REQUIRE_MESSAGE(ecf_port == port,
                              "Expected port " << port << " but found " << ecf_port
                                               << " defs server variables, should be in sync with server");

        const std::string& interval = theServer.defs()->server().find_variable("ECF_INTERVAL");
        BOOST_REQUIRE_MESSAGE(interval == "12",
                              "Expected interval 12 but found "
                                  << interval << " defs server variables, should be in sync with server");
        BOOST_REQUIRE_MESSAGE(theServer.poll_interval() == 12,
                              "Expected poll interval 12 but found " << theServer.poll_interval());

        BOOST_REQUIRE_MESSAGE(theServer.state() == SState::HALTED,
                              "Expected halted at server start but found " << SState::to_string(theServer.state()));
        theServer.halted();
        BOOST_REQUIRE_MESSAGE(theServer.state() == SState::HALTED, "Expected halted ");
        theServer.shutdown();
        BOOST_REQUIRE_MESSAGE(theServer.state() == SState::SHUTDOWN, "Expected shutdown ");
        theServer.restart();
        BOOST_REQUIRE_MESSAGE(theServer.state() == SState::RUNNING, "Expected shutdown ");

        BOOST_REQUIRE_MESSAGE(theServer.lock("fred"), "Expected to lock user fred");
        BOOST_REQUIRE_MESSAGE(theServer.state() == SState::SHUTDOWN, "Locking should shutdown server");
        BOOST_REQUIRE_MESSAGE(theServer.lockedUser() == "fred",
                              "Expected locked user 'fred' but found " << theServer.lockedUser());
        theServer.unlock();
        BOOST_REQUIRE_MESSAGE(theServer.lockedUser().empty(),
                              "Expected no locked user but found " << theServer.lockedUser());
        BOOST_REQUIRE_MESSAGE(theServer.state() == SState::RUNNING, "Expected unlock to restart server ");
    }
}

BOOST_AUTO_TEST_CASE(test_server) {
    cout << "Server:: ...test_server\n";

    // Create a unique port number, allowing debug and release,gnu,clang,intel to run at the same time
    // Hence the lock file is not always sufficient.
    // ECF_FREE_PORT should be unique among  gnu,clang,intel, etc
    std::string the_port1 = "3144";
    char* test_ecf_port   = getenv("ECF_FREE_PORT"); // from metabuilder, allow parallel tests
    if (test_ecf_port)
        the_port1 = test_ecf_port;
    cout << "  Find free port to start server, starting with port " << the_port1 << "\n";

    auto the_port = ecf::convert_to<int>(the_port1);
    while (!EcfPortLock::is_free(the_port))
        the_port++;
    std::string port = ecf::convert_to<std::string>(the_port);
    EcfPortLock::create(port);
    cout << "  Found free port: " << port << " ";

    Host h;
    int count = 0;
    while (1) {
        try {
            test_the_server(port);
            cout << "\n";
            break;
        }
        catch (...) {
            count++;

            // cleanup
            fs::remove(h.ecf_log_file(port));
            EcfPortLock::remove(port);

            cout << " : port " << port << " is used, trying next port\n";

            the_port = ecf::convert_to<int>(port);
            the_port++;

            while (!EcfPortLock::is_free(the_port))
                the_port++;
            port = ecf::convert_to<std::string>(the_port);
            EcfPortLock::create(port);
            cout << "  Found free port: " << port << "\n";

            BOOST_REQUIRE_MESSAGE(count < 20, "Could not find new port after 20 attempts");
        }
    }

    // cleanup
    fs::remove(h.ecf_log_file(port));
    EcfPortLock::remove(port);

    /// Destroy Log singleton to avoid valgrind from complaining
    Log::destroy();
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
