#include "servers.hpp"
#include "server_env_manager.hpp"
#include "service_runner.hpp"
#include "utils/tableprint.hpp"
#include "utils/envmanager.hpp"
#include "utils/directories.hpp"
#include "services.hpp"
#include "config.hpp"
#include "templates.hpp"
#include "contrib/transwarp.hpp"

#include <iostream>
#include <fstream>
#include <iomanip>
#include <filesystem>

namespace dropshell {

std::vector<ServerInfo> get_configured_servers() {
    std::vector<ServerInfo> servers;

    std::vector<std::string> local_config_directories = gConfig().get_local_config_directories();
    if (local_config_directories.empty())
        return servers;

    for (int i = 0; i < local_config_directories.size(); i++) {
        std::string servers_dir = localpath::config_servers(i);
        if (!servers_dir.empty() && std::filesystem::exists(servers_dir)) {

            for (const auto& entry : std::filesystem::directory_iterator(servers_dir)) {
                if (std::filesystem::is_directory(entry)) {
                    std::string server_name = entry.path().filename().string();

                    server_env_manager env(server_name);
                    if (!env.is_valid()) {
                        std::cerr << "Error: Invalid server environment file: " << entry.path().string() << std::endl;
                        continue;
                    }
                    servers.push_back({
                        server_name,
                        env.get_SSH_HOST(),
                        env.get_SSH_USER(),
                        env.get_SSH_PORT()
                    });
                }
            }
        }
    }

    return servers;
}

// https://github.com/bloomen/transwarp?tab=readme-ov-file#range-functions
void list_servers() {
    auto servers = get_configured_servers();

    tableprint tp("All DropShell Servers");
    tp.add_row({"Name", "Address", "Health", "Ports"});

    transwarp::parallel exec{servers.size()};
    auto task = transwarp::for_each(exec, servers.begin(), servers.end(), [&](const ServerInfo& server) {
        std::map<std::string, ServiceStatus> status = service_runner::get_all_services_status(server.name); 

        std::set<int> ports_used;
        std::string serviceticks = "";
        for (const auto& [service_name, service_status] : status) {
            ports_used.insert(service_status.ports.begin(), service_status.ports.end());
            serviceticks += service_runner::HealthStatus2String(service_status.health) + " ";
        }
        std::string ports_used_str = "";
        for (const auto& port : ports_used) 
            ports_used_str += std::to_string(port) + " ";
        
        tp.add_row({server.name, server.ssh_host, serviceticks, ports_used_str});
    });
    task->wait();
    tp.print();
}

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

    //---------------------
    // Check if server is reachable via SSH
    std::string ssh_address = env.get_SSH_HOST();
    std::string ssh_user = env.get_SSH_USER();
    std::string ssh_port = env.get_SSH_PORT();
    if (!ssh_address.empty()) {
        std::cout << std::endl << "Server Status:" << std::endl;
        std::cout << std::string(40, '-') << std::endl;
        
        // Try to connect to the server
        std::string cmd = "ssh -o ConnectTimeout=5 " + ssh_user + "@" + ssh_address + " -p " + ssh_port + " 'echo connected' 2>/dev/null";
        int result = system(cmd.c_str());
        if (result == 0) {
            std::cout << "Status: Online" << std::endl;
            
            // // Get uptime if possible
            // cmd = "ssh " + ssh_address + " 'uptime' 2>/dev/null";
            // int rval = system(cmd.c_str());
            // if (rval != 0) {
            //     std::cout << "Error: Failed to get uptime" << std::endl;
            // } 
        } else {
            std::cout << "Status: Offline" << std::endl;
        }
    }
    std::cout << std::endl;

    //---------------------
    {
        std::cout << std::endl;
        tableprint tp("Server Configuration: " + server_name, true);
        tp.add_row({"Key", "Value"});
        for (const auto& [key, value] : env.get_variables()) {
            tp.add_row({key, value});
        }
        tp.print();
    }

    //---------------------
    // list services, and run healthcheck on each
    {
        tableprint tp("Services: " + server_name, false);
        tp.add_row({"Status", "Service", "Ports"});
        

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

        std::set<int> ports_used;
        std::string serviceticks = "";
        for (const auto& [service_name, service_status] : status) {
            std::string healthy = service_runner::HealthStatus2String(service_status.health);
            
            std::string ports_str = "";
            for (const auto& port : service_status.ports) 
                ports_str += std::to_string(port) + " ";
        
            tp.add_row({healthy, service_name, ports_str});
        } // end of for (const auto& service : services)
        tp.print();
    } // end of list services
} // end of show_server_details

void create_server(const std::string &server_name)
{
    // 1. check if server name already exists
    std::string server_existing_dir = localpath::server(server_name);
    if (!server_existing_dir.empty()) {
        std::cerr << "Error: Server name already exists: " << server_name << std::endl;
        std::cerr << "Current server path: " << server_existing_dir << std::endl;
        return;
    }

    // 2. create a new directory in the user config directory
    std::string config_servers_dir = localpath::config_servers();
    std::string server_dir = config_servers_dir + "/" + server_name;
    std::filesystem::create_directory(server_dir);

    // 3. create a template server.env file in the server directory
    std::string user = getenv("USER");
    std::string server_env_path = server_dir + "/server.env";
    std::ofstream server_env_file(server_env_path);
    server_env_file << "SSH_HOST=" << server_name << std::endl;
    server_env_file << "SSH_USER=" << user << std::endl;
    server_env_file << "SSH_PORT=" << 22 << std::endl;
    server_env_file << std::endl;
    server_env_file << "DROPSHELL_DIR=/home/"+user+"/.dropshell" << std::endl;
    server_env_file.close();

    // 4. add dropshell-agent service to server
    create_service(server_name, "dropshell-agent", "dropshell-agent", true); // silently create service.

    // std::string service_dir = server_dir + "/dropshell-agent";
    // std::filesystem::create_directory(service_dir);
    // std::string service_env_path = service_dir + "/service.env";
    // std::filesystem::copy(get_local_system_templates_path() + "/dropshell-agent/example/service.env", service_env_path);

    std::cout << "Server created successfully: " << server_name << std::endl;
    std::cout << "Please complete the installation:" <<std::endl;
    std::cout << "1) edit the server configuration: dropshell edit " << server_name << std::endl;
    std::cout << "2) test ssh is working:           dropshell ssh " << server_name << std::endl;
    std::cout << "3) install dropshell-agent:       dropshell install " << server_name << " dropshell-agent" << std::endl;
    std::cout << std::endl;
}

void get_all_used_commands(std::set<std::string> &commands)
{
    std::vector<ServerInfo> servers = get_configured_servers();
    for (const auto& server : servers) 
    {
        std::vector<dropshell::ServiceInfo> services = dropshell::get_server_services_info(server.name);
        for (const auto& service : services) 
            commands.merge(dropshell::get_used_commands(server.name, service.service_name));
    }
}

} // namespace dropshell