#include "command_registry.hpp"
#include "config.hpp"
#include "utils/utils.hpp"
#include "utils/directories.hpp"
#include "templates.hpp"
#include "shared_commands.hpp"
#include "utils/hash.hpp"
#include "utils/createagent.hpp"
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <filesystem>
#include "utils/assert.hpp"

namespace dropshell
{

    int install_handler(const CommandContext &ctx);

    static std::vector<std::string> install_name_list = {"install", "reinstall", "update"};

    // Static registration
    struct InstallCommandRegister
    {
        InstallCommandRegister()
        {
            CommandRegistry::instance().register_command({install_name_list,
                                                          install_handler,
                                                          std_autocomplete_allowallservices,
                                                          false, // hidden
                                                          false, // requires_config
                                                          false, // requires_install
                                                          0,     // min_args (after command)
                                                          2,     // max_args (after command)
                                                          "install SERVER [SERVICE]",
                                                          "Install/reinstall service(s). Safe/non-destructive way to update.",
                                                          // heredoc
                                                          R"(
            Install components on a server. This is safe to re-run (non-destructive) and used to update
            servers and their services.

            install                      (re)install dropshell components on this computer.
            install SERVER               (re)install dropshell agent on the given server.
            install SERVER [SERVICE|*]   (re)install the given service (or all services) on the given server.

            Note you need to create the service first with:
                dropshell create-service <server> <template> <service>
            )"});
        }
    } install_command_register;

    // ------------------------------------------------------------------------------------------------
    // rsync_tree_to_remote : SHARED COMMAND
    // ------------------------------------------------------------------------------------------------
    bool rsync_tree_to_remote(
        const std::string &local_path,
        const std::string &remote_path,
        server_env_manager &server_env,
        bool silent)
    {
        ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync.");

        std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " +
                                quote(local_path + "/") + " " +
                                quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" +
                                      remote_path + "/");
        return execute_local_command(rsync_cmd, nullptr, (silent ? cMode::Silent : cMode::Defaults));
    }

    // ------------------------------------------------------------------------------------------------
    // install service over ssh : SHARED COMMAND
    // ------------------------------------------------------------------------------------------------
    bool install_service(const std::string &server, const std::string &service, bool silent)
    {
        LocalServiceInfo service_info = get_service_info(server, service);
        if (!SIvalid(service_info))
            return false;

        server_env_manager server_env(server);
        if (!server_env.is_valid())
            return false;

        maketitle("Installing " + service + " (" + service_info.template_name + ") on " + server);

        if (!server_env.is_valid())
            return false; // should never hit this.

        // Check if template exists
        template_info tinfo = gTemplateManager().get_template_info(service_info.template_name);
        if (!tinfo.is_set())
            return false;

        // Create service directory
        std::string remote_service_path = remotepath::service(server, service);
        std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path);
        if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent))
        {
            std::cerr << "Failed to create service directory " << remote_service_path << std::endl;
            return false;
        }

        // Check if rsync is installed on remote host
        std::string check_rsync_cmd = "which rsync";
        if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", check_rsync_cmd, {}), cMode::Silent))
        {
            std::cerr << "rsync is not installed on the remote host" << std::endl;
            return false;
        }

        // Copy template files
        std::cout << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl
                  << std::string(8, ' ') << "[REMOTE] " << remotepath::service_template(server, service) << "/" << std::endl;
        if (!rsync_tree_to_remote(tinfo.local_template_path().string(), remotepath::service_template(server, service),
                                  server_env, silent))
        {
            std::cerr << "Failed to copy template files using rsync" << std::endl;
            return false;
        }

        // Copy service files
        std::cout << "Copying: [LOCAL] " << localpath::service(server, service) << std::endl
                  << std::string(8, ' ') << "[REMOTE] " << remotepath::service_config(server, service) << std::endl;
        if (!rsync_tree_to_remote(localpath::service(server, service), remotepath::service_config(server, service),
                                  server_env, silent))
        {
            std::cerr << "Failed to copy service files using rsync" << std::endl;
            return false;
        }

        // Run install script
        {
            server_env.run_remote_template_command(service, "install", {}, silent, {});
        }

        // print health tick
        std::cout << "Health: " << healthtick(server, service) << std::endl;
        return true;
    }

    // ------------------------------------------------------------------------------------------------
    // get_arch : SHARED COMMAND
    // ------------------------------------------------------------------------------------------------
    std::string get_arch()
    {
        // determine the architecture of the system
        std::string arch;
#ifdef __aarch64__
        arch = "arm64";
#elif __x86_64__
        arch = "amd64";
#endif
        return arch;
    }

    // ------------------------------------------------------------------------------------------------
    // update_dropshell
    // ------------------------------------------------------------------------------------------------

    std::string _exec(const char *cmd)
    {
        char buffer[128];
        std::string result = "";
        FILE *pipe = popen(cmd, "r");
        if (!pipe)
        {
            throw std::runtime_error("popen() failed!");
        }
        while (!feof(pipe))
        {
            if (fgets(buffer, 128, pipe) != nullptr)
                result += buffer;
        }
        pclose(pipe);
        return trim(result);
    }

    int update_dropshell()
    {
        // determine path to this executable
        std::filesystem::path dropshell_path = std::filesystem::canonical("/proc/self/exe");
        std::filesystem::path parent_path = dropshell_path.parent_path();

        // determine the architecture of the system
        std::string arch = get_arch();

        std::string url = "https://gitea.jde.nz/public/dropshell/releases/download/latest/dropshell." + arch;

        // download new version, preserve permissions and ownership
        std::string bash_script;
        bash_script += "docker run --rm -v " + parent_path.string() + ":/target";
        bash_script += " gitea.jde.nz/public/debian-curl:latest";
        bash_script += " sh -c \"";
        bash_script += " curl -fsSL " + url + " -o /target/dropshell_temp &&";
        bash_script += " chmod --reference=/target/dropshell /target/dropshell_temp &&";
        bash_script += " chown --reference=/target/dropshell /target/dropshell_temp";
        bash_script += "\"";

        std::string cmd = "bash -c '" + bash_script + "'";
        int rval = system(cmd.c_str());
        if (rval != 0)
        {
            std::cerr << "Failed to download new version of dropshell." << std::endl;
            return -1;
        }

        // check if the new version is the same as the old version
        uint64_t new_hash = hash_file(parent_path / "dropshell_temp");
        uint64_t old_hash = hash_file(parent_path / "dropshell");
        if (new_hash == old_hash)
        {
            std::cout << "Confirmed dropshell is the latest version." << std::endl;
            return 0;
        }

        std::string runvercmd = (parent_path / "dropshell").string() + " version";
        std::string currentver = _exec(runvercmd.c_str());
        runvercmd = (parent_path / "dropshell_temp").string() + " version";
        std::string newver = _exec(runvercmd.c_str());

        if (currentver >= newver)
        {
            std::cout << "Current dropshell version: " << currentver << ", published version: " << newver << std::endl;
            std::cout << "No update needed." << std::endl;
            return 0;
        }

        return 0;

        std::string bash_script_2 = "docker run --rm -v " + parent_path.string() + ":/target gitea.jde.nz/public/debian-curl:latest " +
                                    "sh -c \"mv /target/dropshell_temp /target/dropshell\"";
        rval = system(bash_script_2.c_str());
        if (rval != 0)
        {
            std::cerr << "Failed to install new version of dropshell." << std::endl;
            return -1;
        }

        std::cout << "Successfully updated " << dropshell_path << " to the latest " << arch << " version." << std::endl;

        // execute the new version
        execlp("bash", "bash", "-c", (parent_path / "dropshell").c_str(), "install", (char *)nullptr);
        std::cerr << "Failed to execute new version of dropshell." << std::endl;
        return -1;
    }

    int install_local_agent()
    {
        std::vector<std::filesystem::path> paths = {
            gConfig().get_local_template_cache_path(),
            gConfig().get_local_backup_path(),
            gConfig().get_local_tempfiles_path(),
            localpath::agent()};
        for (auto &p : gConfig().get_local_server_definition_paths())
            paths.push_back(p);

        for (auto &p : paths)
            if (!std::filesystem::exists(p))
            {
                std::cout << "Creating directory: " << p << std::endl;
                std::filesystem::create_directories(p);
            }

        // download bb64.
        if (!std::filesystem::exists(localpath::agent() + "bb64"))
        {
            std::string cmd = "cd " + localpath::agent() + " && curl -fsSL -o bb64 https://gitea.jde.nz/public/bb64/releases/download/latest/bb64.amd64 && chmod a+x bb64";
            int ret = system(cmd.c_str());
            if (EXITSTATUSCHECK(ret))
                std::cout << "Downloaded bb64 to " << localpath::agent() << std::endl;
            else
                std::cerr << "Failed to download bb64 to " << localpath::agent() << std::endl;
        }
        else
        {
            std::cout << "Updating bb64..." << std::endl;
            system((localpath::agent() + "bb64 -u").c_str()); // update.
        }
        return 0;
    }

    int install_host()
    {
        // update dropshell.
        // install the local dropshell agent.

        int rval = update_dropshell();
        if (rval != 0)
            return rval;

        rval = install_local_agent();
        if (rval != 0)
            return rval;

        std::cout << "Installation complete." << std::endl;
        return 0;
    }

    int install_server(const std::string &server)
    {
        // install the dropshell agent on the given server.
        std::cout << "Installing dropshell agent on " << server << std::endl;

        std::string agent_path = remotepath::agent(server);
        if (agent_path.empty())
        {
            std::cerr << "Failed to get agent path for " << server << std::endl;
            return 1;
        }

        server_env_manager server_env(server);
        if (!server_env.is_valid())
        {
            std::cerr << "Invalid server environment for " << server << std::endl;
            return 1;
        }

        // first install bb64.
        std::cout << "Installing bb64 on " << server << std::endl
                    << std::flush;
        std::string remote_cmd =
            "ssh -p " + server_env.get_SSH_INFO().port + " " + server_env.get_SSH_INFO().user + "@" + server_env.get_SSH_INFO().host +
            " 'mkdir -p " + quote(agent_path) + " && curl -fsSL \"https://gitea.jde.nz/public/bb64/releases/download/latest/install.sh\" | bash -s -- " +
            quote(agent_path) + " " + quote("$(id -u " + server_env.get_SSH_USER() + "):$(id -g " + server_env.get_SSH_USER() + ")") + "'";

        std::cout << "Executing: " << remote_cmd << std::endl;
        if (!execute_local_command(remote_cmd, nullptr, cMode::Silent))
            std::cerr << "Failed to download bb64 to " << agent_path << " on remote server." << std::endl;
        else
            std::cout << "Downloaded bb64 to " << agent_path << " on remote server." << std::endl;

        // now create the agent.
        create_agent(server);

        return 0; // NOTIMPL
    }

    // ------------------------------------------------------------------------------------------------
    // install command implementation
    // ------------------------------------------------------------------------------------------------
    int install_handler(const CommandContext &ctx)
    {
        if (ctx.args.size() < 1)
        { // install host
            return install_host();
        }

        std::string server = safearg(ctx.args, 0);

        if (ctx.args.size() == 1)
        { // install server
            return install_server(server);
        }

        // install service(s)
        if (safearg(ctx.args, 1) == "*")
        {
            // install all services on the server
            bool okay = true;
            std::vector<LocalServiceInfo> services = get_server_services_info(server);
            for (const auto &service : services)
            {
                if (!install_service(server, service.service_name, false))
                    okay = false;
            }
            return okay ? 0 : 1;
        }
        else
        { // install the specific service.
            std::string service = safearg(ctx.args, 1);
            return install_service(server, service, false) ? 0 : 1;
        }
    }

} // namespace dropshell