    #include <filesystem>
    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <string>
    #include <algorithm>
    #include <iomanip>
    #include <map>

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

    namespace dropshell {

    std::set<std::string> template_source_local::get_template_list() {
        std::set<std::string> templates;
            
        // Helper function to add templates from a directory
        auto add_templates_from_dir = [&templates](const std::string& dir_path) {
            if (!std::filesystem::exists(dir_path)) 
                return;
            
            for (const auto& entry : std::filesystem::directory_iterator(dir_path)) 
                if (entry.is_directory()) 
                    templates.insert(entry.path().filename().string());
        };

        add_templates_from_dir(mLocalPath);
        return templates;
    }

    bool template_source_local::has_template(const std::string& template_name) {
        return std::filesystem::exists(mLocalPath / template_name);
    }

    bool template_source_local::template_command_exists(const std::string& template_name, const std::string& command) {
        std::filesystem::path path = mLocalPath / template_name / (command+".sh");
        return std::filesystem::exists(path);
    }    

    template_info template_source_local::get_template_info(const std::string& template_name) {
        std::filesystem::path path = mLocalPath / template_name;

        if (!std::filesystem::exists(path)) 
            return template_info();

        return template_info(
            template_name,
            mLocalPath.string(),
            path
        );
    }

    void template_manager::list_templates() {
        auto templates = get_template_list();
        
        if (templates.empty()) {
            std::cout << "No templates found." << std::endl;
            return;
        }
        
        std::cout << "Available templates:" << std::endl;

        // print templates.
        std::cout << std::string(60, '-') << std::endl;        
        bool first = true;
        for (const auto& t : templates) {
            std::cout << (first ? "" : ", ") << t;
            first = false;
        }
        std::cout << std::endl;
        std::cout << std::string(60, '-') << std::endl;
    }

    std::set<std::string> template_manager::get_template_list()
    {
        std::set<std::string> templates;
        for (const auto& source : mSources) {
            auto source_templates = source->get_template_list();
            templates.insert(source_templates.begin(), source_templates.end());
        }
        return templates;
    }

    bool template_manager::has_template(const std::string &template_name)
    {
        template_source_interface* source = get_source(template_name);
        if (!source) 
            return false;
        return true;
    }

    template_info template_manager::get_template_info(const std::string &template_name)
    {
        template_source_interface* source = get_source(template_name);
        if (!source) {
            return template_info();
        }
        return source->get_template_info(template_name);
    }

    bool template_manager::template_command_exists(const std::string &template_name, const std::string &command)
    {
        return false;
    }

    void template_manager::create_template(const std::string &template_name)
    {
        // 1. Create a new directory in the user templates directory
        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 init' to initialise DropShell" << std::endl;
            return;
        }

        auto info = get_template_info(template_name);
        if (info.is_set()) {
            std::cerr << "Error: Template '" << template_name << "' already exists at " << info.locationID() << std::endl;
            return;
        }

        auto local_template_paths = gConfig().get_template_local_paths();
        if (local_template_paths.empty()) {
            std::cerr << "Error: No local template paths found" << std::endl;
            std::cerr << "Run 'dropshell edit' to add one to the DropShell config" << std::endl;
            return;
        }
        std::string new_template_path = local_template_paths[0] + "/" + template_name;
        
        // Create the new template directory
        std::filesystem::create_directories(new_template_path);
        
        // 2. Copy the example template from the system templates directory
        auto example_info = gTemplateManager().get_template_info("example-nginx");
        if (!example_info.is_set()) {
            std::cerr << "Error: Example template not found" << std::endl;
            return;
        }
        std::string example_template_path = example_info.local_template_path();
                
        // Copy all files from example template to new template
        for (const auto& entry : std::filesystem::recursive_directory_iterator(example_template_path)) {
            std::string relative_path = entry.path().string().substr(example_template_path.length());
            std::string target_path = new_template_path + relative_path;
            
            if (entry.is_directory()) {
                std::filesystem::create_directory(target_path);
            } else {
                std::filesystem::copy_file(entry.path(), target_path);
            }
        }

        // modify the TEMPLATE=example line in the .template_info.env file to TEMPLATE=<template_name>
        std::string search_string = "TEMPLATE=";
        std::string replacement_line = "TEMPLATE=" + template_name;
        std::string service_env_path = new_template_path + "/config/.template_info.env";
        if (!replace_line_in_file(service_env_path, search_string, replacement_line)) {
            std::cerr << "Error: Failed to replace TEMPLATE= line in the .template_info.env file" << std::endl;
            return;
        }

        // 3. Print out the README.txt file and the path
        std::string readme_path = new_template_path + "/README.txt";
        if (std::filesystem::exists(readme_path)) {
            std::cout << "\nREADME contents:" << std::endl;
            std::cout << std::string(60, '-') << std::endl;
            
            std::ifstream readme_file(readme_path);
            if (readme_file.is_open()) {
                std::string line;
                while (std::getline(readme_file, line)) {
                    std::cout << line << std::endl;
                }
                readme_file.close();
            }
            std::cout << std::string(60, '-') << std::endl;
        } else {
            std::cout << "No README.txt file found in the template." << std::endl;
        }

        std::cout << std::endl;
        std::cout << "Template '" << template_name << "' created at " << new_template_path << std::endl;
        test_template(new_template_path);
    }

    void template_manager::load_sources()
    {
        ASSERT(mSources.empty());
        auto local_template_paths = gConfig().get_template_local_paths();
        if (local_template_paths.empty()) {
            std::cerr << "Error: No local template paths found" << std::endl;
            std::cerr << "Run 'dropshell edit' to add one to the DropShell config" << std::endl;
            return;
        }
        for (const auto& path : local_template_paths) {
            mSources.push_back(std::make_unique<template_source_local>(path));
        }

        auto registry_urls = gConfig().get_template_registry_urls();
        for (const auto& url : registry_urls) {
            mSources.push_back(std::make_unique<template_source_registry>(url));
        }
    }

    bool template_manager::required_file(std::string path, std::string template_name)
    {
        if (!std::filesystem::exists(path)) {
            std::cerr << "Error: " << path << " file not found in template " << template_name << std::endl;
            return false;
        }
        return true;
    }

    template_source_interface *template_manager::get_source(const std::string &template_name)
    {
        for (const auto& source : mSources) {
            if (source->has_template(template_name)) {
                return source.get();
            }
        }
        return nullptr;
    }

    bool template_manager::test_template(const std::string &template_path)
    {
        std::string template_name = std::filesystem::path(template_path).filename().string();

        std::vector<std::string> required_files = {
            "config/service.env",
            "config/.template_info.env",
            "_default.env",
            "install.sh",
            "uninstall.sh",
            "nuke.sh"
        };

        for (const auto& file : required_files) {
            if (!required_file(template_path + "/" + file, template_name)) 
                return false;
        }        

        // ------------------------------------------------------------
        // check TEMPLATE= line.
        std::map<std::string, std::string> all_env_vars;
        std::vector<std::string> env_files = {
            "config/service.env",
            "config/.template_info.env"
        };
        for (const auto& file : env_files) {
            { // load service.env from the service on this machine.
            std::map<std::string, std::string> env_vars;
            envmanager env_manager(template_path + "/" + file);
            env_manager.load();
            env_manager.get_all_variables(env_vars);
            all_env_vars.merge(env_vars);
            }
        }

        // determine template name.
        auto it = all_env_vars.find("TEMPLATE");
        if (it == all_env_vars.end()) {
            std::cerr << "Error: TEMPLATE variable not found in " << template_path << std::endl;
            return false;
        }

        std::string env_template_name = it->second;
        if (env_template_name.empty()) {
            std::cerr << "Error: TEMPLATE variable is empty in " << template_path << std::endl;
            return false;
        }

        if (env_template_name != template_name) {
            std::cerr << "Error: TEMPLATE variable is wrong in " << template_path << std::endl;
            return false;
        }

        return true;
    }

    template_manager & gTemplateManager()
    {
        static template_manager instance;
        return instance;
    }

    std::set<std::string> template_source_registry::get_template_list()
    {
        #pragma message("TODO: Implement template_source_registry::get_template_list")
        return std::set<std::string>();
    }

    bool template_source_registry::has_template(const std::string& template_name)
    {
        #pragma message("TODO: Implement template_source_registry::has_template")
        return false;
    }

    template_info template_source_registry::get_template_info(const std::string& template_name)
    {
        #pragma message("TODO: Implement template_source_registry::get_template_info")
        return template_info();
    }

    bool template_source_registry::template_command_exists(const std::string& template_name, const std::string& command)
    {
        #pragma message("TODO: Implement template_source_registry::template_command_exists")
        return false;
    }

    std::filesystem::path template_source_registry::get_cache_dir()
    {
        #pragma message("TODO: Implement template_source_registry::get_cache_dir")
        return std::filesystem::path();
    }

    } // namespace dropshell
