#include "version.hpp"
#include "config.hpp"
#include "service_runner.hpp"
#include "services.hpp"
#include "servers.hpp"
#include "utils/directories.hpp"
#include "templates.hpp"
#include "utils/utils.hpp"
#include "utils/readmes.hpp"
#include "autocomplete.hpp"
#include "main_commands.hpp"
#include "utils/hash.hpp"

#include <filesystem>
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
#include <chrono>


namespace dropshell {

extern const std::string VERSION;
extern const std::string RELEASE_DATE;
extern const std::string AUTHOR;
extern const std::string LICENSE;

void print_help() {
    std::cout << std::endl;
    maketitle("DropShell version " + VERSION);
    std::cout << std::endl;
    std::cout << "A tool for managing server configurations" << std::endl;
    std::cout << std::endl;
    std::cout << "dropshell ..." << std::endl;
    std::cout << "  help           Show this help message" << std::endl;
    std::cout << "  init DIR       Add DIR as a local server config directory (can add several)" << std::endl;

    if (gConfig().is_config_set()) {
    std::cout << "  server NAME    Show details for specific server" << std::endl;
    std::cout << "  templates      List all available templates" << std::endl;
    std::cout << std::endl;
    std::cout << std::endl;
    std::cout << "Service commands: (if no service is specified, all services for the server are affected)" << std::endl;
    std::cout << "  install SERVER [SERVICE]     Install/reinstall/update service(s). Non-destructive." << std::endl;
    std::cout << "  list [SERVER] [SERVICE]      List status/details of all servers/server/service." << std::endl;
    std::cout << "  edit [SERVER] [SERVICE]      Edit the configuration of dropshell/server/service." << std::endl;
    std::cout << "  COMMAND SERVER [SERVICE]     Run a command on service(s)." << std::endl;
    std::cout << std::endl;
    std::cout << "Standard commands: install, uninstall, backup, restore, start, stop" << std::endl;
    std::cout << std::endl;
    std::cout << "  ssh     SERVER [SERVICE]     Launch an interactive shell on a server or service" << std::endl;
    std::cout << std::endl;
    std::cout << "Creation commands: (apply to the first local config directory)"<<std::endl;
    std::cout << "  create-template TEMPLATE" << std::endl;
    std::cout << "  create-server   SERVER" << std::endl;
    std::cout << "  create-service  SERVER TEMPLATE SERVICE" << std::endl;
    }
    else {
        std::cout << "  edit           Edit the configuration of dropshell" << std::endl;
        std::cout << std::endl;
        std::cout << "Other commands available once initialised." << std::endl;
    }
}


int die(const std::string & msg) {
    std::cerr << msg << std::endl;
    return 1;
}

bool parseargs(std::string arg2, std::string arg3, std::string & server_name, std::vector<ServiceInfo>& servicelist)
{
    if (arg2.empty()) return false;
    server_name = arg2;

    if (arg3.empty()) {
        servicelist = get_server_services_info(server_name);
    } else {
        servicelist.push_back(get_service_info(server_name, arg3));
    }

    return true;
}

std::string safearg(int argc, char *argv[], int index)
{
    if (index >= argc) return "";
    return argv[index];
}

void printversion() {
    maketitle("DropShell version " + VERSION);
    std::cout << "Release date: " << RELEASE_DATE << std::endl;
    std::cout << "Author:       " << AUTHOR << std::endl;
    std::cout << "License:      " << LICENSE << std::endl;
}

#define HAPPYEXIT(CMD, RUNCMD) {if (safearg(argc,argv,1) == CMD) {RUNCMD; return 0;}}

int main(int argc, char* argv[]) {
    HAPPYEXIT("hash", hash_demo_raw(safearg(argc,argv,2)))
    HAPPYEXIT("makesafecmd", std::cout<<makesafecmd(safearg(argc,argv,2))<<std::endl)
    HAPPYEXIT("version", printversion())
    ASSERT_MSG(safearg(argc,argv,1) != "assert", "Hello! Here is an assert.");

    try {
        // silently attempt to load the config file.
        gConfig().load_config();

        if (argc < 2) {
            print_help();
            return 0;
        }
        std::string cmd = argv[1];
        std::vector<std::string> argvec;
        for (int i=0; i<argc; i++) 
            argvec.push_back(argv[i]);


        if (cmd == "autocomplete") {
            autocomplete(argvec);
            return 0;
        }

        if (cmd == "init") {
            return main_commands::init(argvec);
        }

        if (cmd == "help" || cmd == "-h" || cmd == "--help" || cmd== "h" || cmd=="halp") {
            print_help();
            return 0;
        }

        if (cmd == "edit" && argc < 3) {
            std::string config_file = localfile::dropshell_env();
            std::filesystem::create_directories(  get_parent(config_file) );
            edit_file(config_file, "Please ensure any directories you have introduced in the config file exist.");
            return 0;
        }

        // ------------------------------------------------------------
        // from here we require the config file to be loaded.
        if (!gConfig().is_config_set()) 
            return die("Please run 'dropshell init <path>' to initialise the user directory and create a configuration file.");

        const std::vector<std::string> & local_config_directories = gConfig().get_local_config_directories();
        std::cout << "Config directories: ";
        for (auto & dir : local_config_directories) 
            std::cout << "["<< dir << "] ";
        std::cout << std::endl;

        if (cmd == "server" || cmd == "servers" || cmd == "list" || cmd == "view") 
            switch (argc)
            {
                case 2:
                    list_servers();
                    return 0;
                case 3:
                    show_server_details(argv[2]);
                    return 0;
                case 4:
                    cmd="logs";
                    break;
                default:
                    return die("dropshell server: too many arguments");
            }

        if (cmd == "templates") {
            list_templates();
            return 0;
        }

        if (cmd == "create-template") {
            if (argc < 3) return die("Error: create-template requires a template name");
            create_template(argv[2]);
            return 0;
        }

        if (cmd == "create-server") {
            if (argc < 3) return die("Error: create-server requires a server name");
            create_server(argv[2]);
            return 0;
        }

        if (cmd == "create-service") {
            if (argc < 5) return die("Error: not enough arguments.\ndropshell create-service server template service");
            create_service(argv[2], argv[3], argv[4]);
            return 0;
        }

         if (cmd == "ssh" && argc < 4) {
            if (argc < 3) return die("Error: ssh requires a server name and optionally service name");
            interactive_ssh(argv[2], "bash");
            return 0;
        }

        if (cmd == "edit" && argc < 4) {
            ASSERT_MSG(argc>=3, "Error: logic error!");
            edit_server(safearg(argc,argv,2));
            return 0;
        }

        if (cmd == "backup" || cmd=="backups") {
            if (argc < 4) return die("Error: backup requires a target server and target service to back up");
            return main_commands::backup(argvec);
        }

        if (cmd == "restore") {
            if (argc < 4) return die("Error: restore requires a target server, target service the backup file to restore");
            return main_commands::restore(argvec);
        }

        // handle running a command. 
        std::set<std::string> commands;
        get_all_used_commands(commands);
        commands.merge(std::set<std::string>{"ssh","edit","_allservicesstatus"}); // handled by service_runner, but not in template_shell_commands.
        for (const auto& command : commands) {
            if (cmd == command) {
                std::string server_name;
                std::vector<ServiceInfo> servicelist;
                if (!parseargs(safearg(argc, argv, 2), safearg(argc, argv, 3), server_name, servicelist)) {
                    std::cerr << "Error: " << command << " command requires server name and optionally service name" << std::endl;
                    return 1;
                }

                for (const auto& service_info : servicelist) {
                    service_runner runner(server_name, service_info.service_name);
                    if (!runner.isValid()) {
                        std::cerr << "Error: Failed to initialize service" << std::endl;
                        return 1;
                    }
                    if (!runner.run_command(command)) {
                        std::cerr << command +" failed." << std::endl;
                        return 1;
                    }
                }
                return 0;
            }
        }

        // Unknown command
        std::cerr << "Error: Unknown command '" << cmd << "'" << std::endl;
        std::cerr << "Valid commands: ";
        for (const auto& command : commands) {
            if (!command.empty() && command[0]!='_')
                std::cerr << command << " ";
        }
        std::cerr << std::endl;
        return 1;

    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
} 

} // namespace dropshell

int main(int argc, char* argv[]) {
    return dropshell::main(argc, argv);
}