#include "config.hpp"
#include "service_runner.hpp"
#include "server_env_manager.hpp"
#include "templates.hpp"
#include "services.hpp"
#include "utils/directories.hpp"
#include "utils/utils.hpp"
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <chrono>
#include <iomanip>
#include <filesystem>
#include <unistd.h>

namespace fs = std::filesystem;

namespace dropshell {

service_runner::service_runner(const std::string& server_name, const std::string& service_name) : 
    mServerEnv(server_name), mServer(server_name), mService(service_name), mValid(false)
{
    if (server_name.empty() || service_name.empty())
        return; 

    // Initialize server environment
    if (!mServerEnv.is_valid()) 
        return;

    mServiceInfo = get_service_info(server_name, service_name);
    mService = mServiceInfo.service_name;
    
    mValid = !mServiceInfo.local_template_path.empty();
}

bool service_runner::install() {
    maketitle("Installing " + mService + " (" + mServiceInfo.template_name + ") on " + mServer);

    if (!mServerEnv.is_valid()) return false; // should never hit this.
    
    // Check if template exists
    template_info tinfo;
    if (!get_template_info(mServiceInfo.template_name, tinfo))
        return false;
        
    // Create service directory
    std::string remote_service_path = remotepath::service(mServer, mService);
    std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path);
    if (!mServerEnv.execute_ssh_command(mkdir_cmd))
    {
        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 > /dev/null 2>&1";
    if (!mServerEnv.execute_ssh_command(check_rsync_cmd))
    {
        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(mServer, mService) << "/" << std::endl;
        std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + mServerEnv.get_SSH_PORT() + "' " + 
                            quote(tinfo.local_template_path + "/") + " "+ 
                            mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + 
                            quote(remotepath::service_template(mServer, mService)+"/");
        //std::cout << std::endl << rsync_cmd << std::endl << std::endl;
        if (!mServerEnv.execute_local_command(rsync_cmd))
        {
            std::cerr << "Failed to copy template files using rsync" << std::endl;
            std::cerr << "Is rsync installed on the remote host?" << std::endl;
            return false;
        }
    }
    
    // Copy service files (including service.env)
    {
        std::string local_service_path = localpath::service(mServer,mService);
        if (local_service_path.empty() || !fs::exists(local_service_path)) {
            std::cerr << "Error: Service directory not found: " << local_service_path << std::endl;
            return false;
        }
        std::cout << "Copying: [LOCAL] " << local_service_path << std::endl <<std::string(8,' ')<<"[REMOTE] " << remotepath::service_config(mServer,mService) << std::endl; 
        std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + mServerEnv.get_SSH_PORT() + "' " + 
                            quote(local_service_path + "/") + " "+
                            mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" + 
                            quote(remotepath::service_config(mServer,mService) + "/");
        if (!mServerEnv.execute_local_command(rsync_cmd))
        {
            std::cerr << "Failed to copy service files using rsync" << std::endl;
            return false;
        }
    }

    // Run install script
    {
        mServerEnv.run_remote_template_command(mService, "install", {});
    }

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

bool service_runner::uninstall() {
    maketitle("Uninstalling " + mService + " (" + mServiceInfo.template_name + ") on " + mServer);
    
    if (!mServerEnv.is_valid()) return false; // should never hit this.
    
    // 2. Check if service directory exists on server
    if (!mServerEnv.check_remote_dir_exists(remotepath::service(mServer, mService))) {
        std::cerr << "Service is not installed: " << mService << std::endl;
        return true; // Nothing to uninstall
    }
    
    // 3. Run uninstall script if it exists
    std::string uninstall_script = remotepath::service_template(mServer, mService) + "/_uninstall.sh";
    bool script_exists = mServerEnv.check_remote_file_exists(uninstall_script);
    
    if (script_exists) {
        if (!mServerEnv.run_remote_template_command(mService, "uninstall", {})) {
            std::cerr << "Warning: Uninstall script failed, but continuing with directory removal" << std::endl;
        }

    } else {
        std::cerr << "Warning: No uninstall script found. Unable to uninstall service." << std::endl;
        return false;
    }
    
    // 4. Remove the service directory from the server
    std::string rm_cmd = "'rm -rf " + quote(remotepath::service(mServer, mService)) + "'";
    if (!mServerEnv.execute_ssh_command(rm_cmd)) {
        std::cerr << "Failed to remove service directory" << std::endl;
        return false;
    }
    
    std::cout << "Service " << mService << " successfully uninstalled from " << mServer << std::endl;
    return true;
}


// ------------------------------------------------------------------------------------------------
// Run a command on the service.
// ------------------------------------------------------------------------------------------------
bool service_runner::run_command(const std::string& command) {
    if (!mServerEnv.is_valid()) {
        std::cerr << "Error: Server service not initialized" << std::endl;
        return false;
    }
    template_info tinfo;
    if (!get_template_info(mServiceInfo.template_name, tinfo)) {
        std::cerr << "Error: Template '" << mServiceInfo.template_name << "' not found" << std::endl;
        return false;
    }

    // don't need a script for edit!
    if (command == "edit") {
        edit_service_config();
        return true;
    }

    if (!template_command_exists(mServiceInfo.template_name, command)) {
        std::cout << "No command script for " << mServiceInfo.template_name << " : " << command << std::endl;
        return true; // nothing to run.
    }

    // install doesn't require anything on the server yet.
    if (command == "install") 
        return install();

    std::string script_path = remotepath::service_template(mServer, mService) + "/" + command + ".sh";
    
    // Check if service directory exists
    if (!mServerEnv.check_remote_dir_exists(remotepath::service(mServer, mService))) {
        return false;
    }
    
    // Check if command script exists
    if (!mServerEnv.check_remote_file_exists(script_path)) {
        return false;
    }

    // Check if env file exists
    if (!mServerEnv.check_remote_file_exists(remotefile::service_env(mServer, mService))) {
        return false;
    }

    if (command == "uninstall") 
        return uninstall();
    if (command == "ssh") {
        interactive_ssh_service();
        return true;
    }

    // Run the generic command
    std::vector<std::string> args; // not passed through yet.
    return mServerEnv.run_remote_template_command(mService, command, args);
}


std::map<std::string, ServiceStatus> service_runner::get_all_services_status(std::string server_name)
{
    std::map<std::string, ServiceStatus> status;

    std::string command = "_allservicesstatus";
    std::string service_name = "dropshell-agent";

    if (!template_command_exists(service_name, command))
        {
            std::cerr << "Error: " << service_name << " does not contain the " << command << " script" << std::endl;
            return status;
        }

    server_env_manager env(server_name);
    if (!env.is_valid()) {
        std::cerr << "Error: Invalid server environment" << std::endl;
        return status;
    }

    std::string output;
    if (!env.run_remote_template_command_and_capture_output(service_name, command, {}, output))
        return status;

    std::stringstream ss(output);
    std::string line;
    while (std::getline(ss, line)) {
        std::string key, value;
        std::size_t pos = line.find("=");
        if (pos != std::string::npos) {
            key = dequote(trim(line.substr(0, pos)));
            value = dequote(trim(line.substr(pos + 1)));

            // decode key, it's of format SERVICENAME_[HEALTH|PORTS]
            std::string service_name = key.substr(0, key.find_last_of("_"));
            std::string status_type = key.substr(key.find_last_of("_") + 1);

            if (status_type == "HEALTH") { // healthy|unhealthy|unknown
                if (value == "healthy")
                    status[service_name].health = HealthStatus::HEALTHY;
                else if (value == "unhealthy")
                    status[service_name].health = HealthStatus::UNHEALTHY;
                else if (value == "unknown")
                    status[service_name].health = HealthStatus::UNKNOWN;
                else
                    status[service_name].health = HealthStatus::ERROR;
            } else if (status_type == "PORTS") { // port1,port2,port3
                std::vector<std::string> ports = string2multi(value);
                for (const auto& port : ports) {
                    if (port!="unknown")
                        status[service_name].ports.push_back(str2int(port));
                }
            }
        }
    }
    return status;
}

HealthStatus service_runner::is_healthy()
{
    if (!mServerEnv.is_valid()) {
        std::cerr << "Error: Server service not initialized" << std::endl;
        return HealthStatus::ERROR;
    }

    if (!mServerEnv.check_remote_dir_exists(remotepath::service(mServer, mService))) {
        return HealthStatus::NOTINSTALLED;
    }

    std::string script_path = remotepath::service_template(mServer, mService) + "/status.sh";
    if (!mServerEnv.check_remote_file_exists(script_path)) {
        return HealthStatus::UNKNOWN;
    }
    
    // Run status script, does not display output.
    if (!mServerEnv.run_remote_template_command(mService, "status", {}, true))
        return HealthStatus::UNHEALTHY;
    return HealthStatus::HEALTHY;
}

std::string service_runner::healthtick()
{
    std::string green_tick = "\033[32m✓\033[0m";
    std::string red_cross = "\033[31m✗\033[0m";    
    std::string yellow_exclamation = "\033[33m!\033[0m";
    std::string unknown = "\033[37m✓\033[0m";

    HealthStatus status = is_healthy();
    if (status == HealthStatus::HEALTHY)
        return green_tick;
    else if (status == HealthStatus::UNHEALTHY)
        return red_cross;
    else if (status == HealthStatus::UNKNOWN)
        return unknown;
    else
        return yellow_exclamation;
}

std::string service_runner::HealthStatus2String(HealthStatus status)
{
    if (status == HealthStatus::HEALTHY)
        return ":tick:";
    else if (status == HealthStatus::UNHEALTHY)
        return ":cross:";
    else if (status == HealthStatus::UNKNOWN)
        return ":greytick:";
    else if (status == HealthStatus::NOTINSTALLED)
        return ":warning:";
    else
        return ":error:";
}

bool service_runner::ensure_service_dropshell_files_up_to_date()
{
    if (!mServerEnv.is_valid()) {
        std::cerr << "Error: Server service not initialized" << std::endl;
        return false;
    }
    
    // check if the service template and config are up to date on the remote server.
    service_versions versions(mServer, mService);
    if (versions.remote_up_to_date())
        return true;
    
    if (!versions.remote_template_is_up_to_date()) {
        std::cerr << "Error: Service template is not up to date on the remote server" << std::endl;
        return false;
    }

    if (!versions.remote_config_is_up_to_date()) {
        std::cerr << "Error: Service config is not up to date on the remote server" << std::endl;
        return false;
    }

// TODO - actually update things!

    versions.update_stored_remote_versions();
    return versions.remote_up_to_date();
}

std::string service_runner::healthmark()
{
    HealthStatus status = is_healthy();
    return HealthStatus2String(status);
}

void interactive_ssh(const std::string & server_name, const std::string & command) {
    std::string serverpath = localpath::server(server_name);
    if (serverpath.empty()) {
        std::cerr << "Error: Server not found: " << server_name << std::endl;
        return;
    }

    server_env_manager env(server_name);
    if (!env.is_valid()) {
        std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
        return;
    }

    std::string ssh_address = env.get_SSH_HOST();
    std::string ssh_user = env.get_SSH_USER();
    std::string ssh_port = env.get_SSH_PORT();

    std::string login = ssh_user + "@" + ssh_address;

    // Execute ssh with server_name and command
    if (command.empty())
        execlp("ssh", "ssh", "-tt", login.c_str(), "-p", ssh_port.c_str(), nullptr);
    else
        execlp("ssh", "ssh", "-tt", login.c_str(), "-p", ssh_port.c_str(), command.c_str(), nullptr);
    
    // If exec returns, it means there was an error
    perror("ssh execution failed");
    exit(EXIT_FAILURE);
}

void edit_server(const std::string &server_name)
{
    std::string serverpath = localpath::server(server_name);
    if (serverpath.empty()) {
        std::cerr << "Error: Server not found: " << server_name << std::endl;
        return;
    }

    std::ostringstream aftertext; 
    aftertext << "If you have changed DROPSHELL_DIR, you should manually move the files to the new location NOW.\n"
              << "You can ssh in to the remote server with:  dropshell ssh "<<server_name<<"\n" 
              << "Once moved, reinstall all services with:   dropshell install " << server_name;

    std::string config_file = serverpath + "/server.env";
    edit_file(config_file, aftertext.str());
}

void edit_file(const std::string &file_path, const std::string & aftertext)
{
    std::string cmd = "nano -w "+file_path+" ; echo \""+aftertext+"\"";
    execlp("bash","bash","-c",cmd.c_str(), nullptr);
    
    // If exec returns, it means there was an error
    perror("ssh execution failed");
    exit(EXIT_FAILURE);
}

bool service_runner::restore(std::string backup_file)
{
    std::string command = "restore";
    std::string script_path = remotepath::service_template(mServer, mService) + "/" + command + ".sh";
    if (!template_command_exists(mServiceInfo.template_name, command)) {
        std::cout << "No restore script for " << mServiceInfo.template_name << std::endl;
        return true; // nothing to restore.
    }

    /// TOODOOOOOO!!!!!!
    std::cout << "Restore not implemented yet" << std::endl;
    return true;

    //    std::string run_cmd = construct_standard_command_run_cmd("restore");
    //    return execute_ssh_command(run_cmd, "Restore script failed");
}

void service_runner::interactive_ssh_service()
{
    std::set<std::string> used_commands = get_used_commands(mServer, mService);
    if (used_commands.find("ssh") == used_commands.end()) {
        std::cerr << "Error: "<< mService <<" does not support ssh" << std::endl;
        return;
    }

    std::vector<std::string> args; // not passed through yet.
    mServerEnv.run_remote_template_command(mService, "ssh", args);
}

void service_runner::edit_service_config()
{
    std::string config_file = localfile::service_env(mServer,mService);
    if (!fs::exists(config_file)) {
        std::cerr << "Error: Service config file not found: " << config_file << std::endl;
        return;
    }

    std::string aftertext = "To apply your changes, run:\n   dropshell install " + mServer + " " + mService;

    edit_file(config_file, aftertext);
}

} // namespace dropshell 