#include "services.hpp"
#include "utils/envmanager.hpp"
#include "utils/directories.hpp"
#include "templates.hpp"
#include "config.hpp"
#include "utils/utils.hpp"
#include "server_env_manager.hpp"
#include "servers.hpp"
#include "assert.hpp"

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

namespace dropshell {   

    bool SIvalid(const LocalServiceInfo& service_info) {
        return !service_info.service_name.empty() && 
               !service_info.template_name.empty() && 
               !service_info.local_service_path.empty() && 
               !service_info.local_template_path.empty();
    }

std::vector<LocalServiceInfo> get_server_services_info(const std::string& server_name) {
    std::vector<LocalServiceInfo> services;

    if (server_name.empty())
        return services;

    std::vector<std::string> local_server_definition_paths = gConfig().get_local_server_definition_paths();
    if (local_server_definition_paths.empty()) {
        std::cerr << "Error: No local server definition paths found" << std::endl;
        std::cerr << "Run 'dropshell edit' to configure DropShell" << std::endl;
        return services;
    }

    for (const auto& server_definition_path : local_server_definition_paths) {
        fs::path serverpath = server_definition_path + "/" + server_name;
        if (fs::exists(serverpath)) // service is on that server...
            for (const auto& entry : fs::directory_iterator(serverpath)) {
                if (fs::is_directory(entry)) {
                    std::string dirname = entry.path().filename().string();
                    if (dirname.empty() || dirname[0] == '.' || dirname[0] == '_')
                        continue;
                    auto service = get_service_info(server_name, dirname);
                    if (!service.local_service_path.empty()) 
                        services.push_back(service);
                    else 
                        warning << "Failed to get service info for " << dirname << " on server " << server_name << std::endl;
                }
            } // end of for 
    }
   
    return services;
}


LocalServiceInfo get_service_info(const std::string &server_name, const std::string &service_name)
{
    LocalServiceInfo service;

    if (server_name.empty() || service_name.empty())
        return LocalServiceInfo();

    service.service_name = service_name;

    service.local_service_path = localpath::service(server_name, service_name);
    if (service.local_service_path.empty()) 
        return LocalServiceInfo();

    // check the service directory exists.
    if (!fs::exists(service.local_service_path))
    {
        std::cerr << "Error: Service directory not found: " << service.local_service_path << std::endl;
        return LocalServiceInfo();
    }

    // now set the template name and path.
    std::map<std::string, std::string> variables;
    if (!get_all_service_env_vars(server_name, service_name, variables))
        return LocalServiceInfo();
 
    // confirm TEMPLATE is defined.
    auto it = variables.find("TEMPLATE");
    if (it == variables.end()) {
        std::cerr << "Error: TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl;
        return LocalServiceInfo();
    }
    service.template_name = it->second;

    template_info tinfo = gTemplateManager().get_template_info(service.template_name);
    if (!tinfo.is_set()) {
        std::cerr << "Error: Template '" << service.template_name << "' not found" << std::endl;
        return LocalServiceInfo();
    }

    // find the template path   
    service.local_template_path = tinfo.local_template_path();

    return service;
}

std::set<std::string> get_used_commands(const std::string &server_name, const std::string &service_name)
{
    std::set<std::string> commands;

    if (server_name.empty() || service_name.empty())
        return commands;

    auto service_info = get_service_info(server_name, service_name);
    if (service_info.local_template_path.empty()) {
        std::cerr << "Error: Service not found: " << service_name << std::endl;
        return commands;
    }

    // iterate over all files in the template path, and add the command name to the set.
    // commands are .sh files that don't begin with _
    for (const auto& entry : fs::directory_iterator(service_info.local_template_path)) {
        if (fs::is_regular_file(entry) && entry.path().extension() == ".sh" && (entry.path().filename().string().rfind("_", 0) != 0)) 
            commands.insert(entry.path().stem().string());
    }
    return commands;
}

std::set<std::string> list_backups(const std::string &server_name, const std::string &service_name)
{
    std::set<std::string> backups;

    if (server_name.empty() || service_name.empty())
        return backups;

    // need to find the template for the service.
    auto service_info = get_service_info(server_name, service_name);
    if (service_info.local_template_path.empty()) {
        std::cerr << "Error: Service not found: " << service_name << std::endl;
        return backups;
    }

    std::string backups_dir = gConfig().get_local_backup_path();
    if (backups_dir.empty())
        return backups;

    if (fs::exists(backups_dir)) {
        for (const auto& entry : fs::directory_iterator(backups_dir)) {
            if (fs::is_regular_file(entry) && entry.path().extension() == ".tgz")
                if (entry.path().filename().string().find(service_info.template_name) != std::string::npos)
                {
                    backups.insert(entry.path().filename().string());
                }
        }
    }
    return backups;
}



bool get_all_service_env_vars(const std::string &server_name, const std::string &service_name, std::map<std::string, std::string> & all_env_vars)
{
    all_env_vars.clear();

    if (localpath::service(server_name, service_name).empty() || !fs::exists(localpath::service(server_name, service_name)))
    {
        std::cerr << "Error: Service not found: " << service_name << " on server " << server_name << std::endl;
        return false;
    }

    ServerInfo server_info = get_server_info(server_name);
    if (server_info.ssh_host.empty()) 
        std::cerr << "Error: Server " << server_name << " not found - ssh_host empty, so HOST_NAME not set" << std::endl;

    // add in some handy variables.
    // if we change these, we also need to update agent/_allservicesstatus.sh
    all_env_vars["CONFIG_PATH"] = remotepath::service_config(server_name,service_name);
    all_env_vars["SERVER"] = server_name;
    all_env_vars["SERVICE"] = service_name;
    all_env_vars["AGENT_PATH"] = remotepath::agent(server_name);
    all_env_vars["HOST_NAME"] = server_info.ssh_host;
    all_env_vars["DOCKER_CLI_HINTS"] = "false"; // turn off docker junk.

    // Lambda function to load environment variables from a file
    auto load_env_file = [&all_env_vars](const std::string& file) {
        if (!file.empty() && std::filesystem::exists(file)) {
            std::map<std::string, std::string> env_vars;
            envmanager env_manager(file);
            env_manager.load();
            env_manager.get_all_variables(env_vars);
            all_env_vars.merge(env_vars);
        }
        else
            warning << "Expected environment file not found: " << file << std::endl;
    };

    // Load environment files
    load_env_file(localfile::service_env(server_name, service_name));
    load_env_file(localfile::template_info_env(server_name, service_name));

    // determine template name.
    auto it = all_env_vars.find("TEMPLATE");
    if (it == all_env_vars.end()) {
        error << "TEMPLATE variable not defined in service " << service_name << " on server " << server_name << std::endl;
        info << "The TEMPLATE variable is required to determine the template name." << std::endl;
        info << "Please check the service.env file and the .template_info.env file in:" << std::endl;
        info << "    " << localpath::service(server_name, service_name) << std::endl << std::endl;
        return false;
    }
    template_info tinfo = gTemplateManager().get_template_info(it->second);
    if (!tinfo.is_set()) {
        std::cerr << "Error: Template '" << it->second << "' not found" << std::endl;
        return false;
    }

    std::string default_env_file = tinfo.local_template_path()/"_default.env";
    if (!fs::exists(default_env_file)) {
        std::cerr << "Error: Template default env file '" << default_env_file << "' not found" << std::endl;
        return false;
    }

    load_env_file(default_env_file);
    return true;
}

} // namespace dropshell