/*
 * 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 <boost/test/unit_test.hpp>

#include "InvokeServer.hpp"
#include "SCPort.hpp"
#include "ecflow/client/ClientEnvironment.hpp"
#include "ecflow/client/ClientInvoker.hpp"
#include "ecflow/core/PasswdFile.hpp"

using namespace std;
using namespace ecf;

BOOST_AUTO_TEST_SUITE(S_Client)

BOOST_AUTO_TEST_SUITE(T_PasswdFile)

// ************************************************************************************
// Note: If you make edits to node tree, they will have no effect until the server is rebuilt
//
// Note: To test HPUX->Linux, invoke serve on (Linux/HPUX) and the client cmds on other system
//       On the client side set ECF_HOST to machine name. To allow further testing if ECF_HOST
//       is specified then *don't* shutdown the server
// ************************************************************************************

class Add_ECF_PASSWD_env {
public:
    explicit Add_ECF_PASSWD_env(const std::string& passwd_file) : env_("ECF_PASSWD=") {
        env_ += passwd_file;
        auto* put = const_cast<char*>(env_.c_str());
        BOOST_CHECK_MESSAGE(putenv(put) == 0, "putenv failed for " << put);
    }
    ~Add_ECF_PASSWD_env() {
        putenv(const_cast<char*>("ECF_PASSWD")); // remove from env, otherwise valgrind complains
    }

private:
    std::string env_;
};

BOOST_AUTO_TEST_CASE(test_loading_of_passwd) {
    Host the_host;
    std::string host        = ClientEnvironment::hostSpecified();
    std::string port        = SCPort::next();
    std::string passwd_file = the_host.ecf_passwd_file(port);
    std::string passwd      = "xxxx";
    if (host.empty()) {
        // make sure NO passed file is present before the server is started.
        // Only do this locally, as passwd file on remote machine may not be accessible
        fs::remove(passwd_file);
    }
    else {
        // The test only works if server is started locally.
        std::cout << "...Client:: ...test_loading_of_passwd ignoring when HOST specified\n";
        return;
    }

    // cout << "passwd_file " << passwd_file << "\n";

    // Set ECF_PASSWD environment variable. Use same file for client and server
    // add on construction, and remove at destruction.
    Add_ECF_PASSWD_env ecf_passwd(passwd_file);

    // Create a valid passwd file; Before server start
    std::string errorMsg;
    BOOST_REQUIRE_MESSAGE(PasswdFile::createWithAccess(passwd_file, the_host.name(), port, passwd, errorMsg), errorMsg);

    // This will remove check pt and backup file before server start,
    // to avoid the server from loading previous test data
    InvokeServer invokeServer("Client:: ...test_loading_of_passwd", port);
    BOOST_REQUIRE_MESSAGE(invokeServer.server_started(),
                          "Server failed to start on " << invokeServer.host() << ":" << invokeServer.port());

    ClientInvoker theClient(invokeServer.host(), invokeServer.port());
    theClient.set_throw_on_error(false);

    BOOST_REQUIRE_MESSAGE(theClient.delete_all() == 0,
                          CtsApi::to_string(CtsApi::delete_node()) << " should return 0\n"
                                                                   << theClient.errorMsg());
    BOOST_REQUIRE_MESSAGE(theClient.shutdownServer() == 0,
                          CtsApi::shutdownServer() << " should return 0\n"
                                                   << theClient.errorMsg());
    BOOST_REQUIRE_MESSAGE(theClient.haltServer() == 0,
                          CtsApi::haltServer() << " should return 0\n"
                                               << theClient.errorMsg());
    BOOST_REQUIRE_MESSAGE(theClient.restartServer() == 0,
                          CtsApi::restartServer() << " should return 0\n"
                                                  << theClient.errorMsg());

    // reloading the same passwd file succeed.
    BOOST_REQUIRE_MESSAGE(theClient.reloadpasswdfile() == 0, CtsApi::reloadpasswdfile() << " should return 1\n");

    // Invoking a client request that requires authorisation
    BOOST_CHECK_MESSAGE(theClient.shutdownServer() == 0, "should return 0\n" << theClient.errorMsg());
    BOOST_CHECK_MESSAGE(theClient.getDefs() == 0, "should return 0\n" << theClient.errorMsg());
    BOOST_CHECK_MESSAGE(theClient.sync_local() == 0, "should return 0\n" << theClient.errorMsg());
    BOOST_CHECK_MESSAGE(theClient.news_local() == 0, "should return 0\n" << theClient.errorMsg());

    // clear the password file.
    errorMsg.clear();
    BOOST_REQUIRE_MESSAGE(PasswdFile::clear(passwd_file, errorMsg), errorMsg);

    // reload the password file, which is now empty
    BOOST_REQUIRE_MESSAGE(theClient.reloadpasswdfile() == 0, CtsApi::reloadpasswdfile() << " should return 1\n");

    // all client command should now *FAIL*.
    BOOST_CHECK_MESSAGE(theClient.shutdownServer() == 1, "should return 1\n");
    BOOST_REQUIRE_MESSAGE(theClient.reloadpasswdfile() == 1, CtsApi::reloadpasswdfile() << " should return 1\n");

    // Remove the white list file. Comment out for debug
    fs::remove(passwd_file);
}

BOOST_AUTO_TEST_CASE(test_loading_of_passwd_fail) {
    // TEST user *MUST* be in ECF_PASSWD file, into order to *ALLOW* reloadpasswdfile
    Host the_host;
    std::string host        = ClientEnvironment::hostSpecified();
    std::string port        = SCPort::next();
    std::string passwd_file = the_host.ecf_passwd_file(port);
    std::string passwd      = "xxxx";
    if (host.empty()) {
        // make sure NO passed file is present before the server is started.
        // Only do this locally, as passwd file on remote machine may not be accessible
        fs::remove(passwd_file);
    }
    else {
        // The test only works if server is started locally.
        std::cout << "...Client:: ...test_loading_of_passwd_fail ignoring when HOST specified\n";
        return;
    }

    // Set ECF_PASSWD environment variable. Use same file for client and server
    // add on construction, and remove at destruction.
    Add_ECF_PASSWD_env ecf_passwd(passwd_file);

    // This will remove check pt and backup file before server start,
    // to avoid the server from loading previous test data
    InvokeServer invokeServer("Client:: ...test_loading_of_passwd_fail", port);
    BOOST_REQUIRE_MESSAGE(invokeServer.server_started(),
                          "Server failed to start on " << invokeServer.host() << ":" << invokeServer.port());

    /// Passwd file is specified but does not exist. Command to succeed
    ClientInvoker theClient(invokeServer.host(), invokeServer.port());
    theClient.set_throw_on_error(false);

    BOOST_REQUIRE_MESSAGE(theClient.delete_all() == 0,
                          CtsApi::to_string(CtsApi::delete_node()) << " should return 0\n"
                                                                   << theClient.errorMsg());
    BOOST_REQUIRE_MESSAGE(theClient.shutdownServer() == 0,
                          CtsApi::shutdownServer() << " should return 0\n"
                                                   << theClient.errorMsg());
    BOOST_REQUIRE_MESSAGE(theClient.haltServer() == 0,
                          CtsApi::haltServer() << " should return 0\n"
                                               << theClient.errorMsg());
    BOOST_REQUIRE_MESSAGE(theClient.restartServer() == 0,
                          CtsApi::restartServer() << " should return 0\n"
                                                  << theClient.errorMsg());

    // OK now test passwd file functionality
    // The passwd file should not exist, hence reload SHOULD fail. i.e because we deleted it earlier
    BOOST_REQUIRE_MESSAGE(theClient.reloadpasswdfile() == 1, CtsApi::reloadpasswdfile() << " should return 1\n");

    // Create a valid passwd file; For the FIRST time:
    std::string errorMsg;
    BOOST_REQUIRE_MESSAGE(PasswdFile::createWithAccess(passwd_file, the_host.name(), port, passwd, errorMsg), errorMsg);

    //  Reload should STILL fail. Since reloadpasswdfile *ITSELF* requires authentication.
    //  Hence user *MUST* be in ECF_PASSWD file, into order to *ALLOW* reloadpasswdfile
    BOOST_REQUIRE_MESSAGE(theClient.reloadpasswdfile() == 1,
                          CtsApi::reloadpasswdfile() << " should return 1\n"
                                                     << theClient.errorMsg());

    // Remove the white list file. Comment out for debug
    fs::remove(passwd_file);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
