/*
    dropshell-tool

    dropshell-tool install <tool_name>
        - confirms dropshell-tool is fully initialised:
            - adds a line to the user's .bashrc file to source the dropshell-tool ~/.bashrc_dropshell_tool script, if it doesn't exist
            - creates an empty ~/.bashrc_dropshell_tool script if it doesn't exist
            - creates the ~/.config/dropshell-tool directory, if it does not exist
            - creates the ~/.local/bin/dropshell-tool directory, if it does not exist

        - removes the tool from the user's system if it is already installed, and the tool's entries in the ~/.bashrc_dropshell_tool script
        - downloads the tool archive (tgz) from tools.dropshell.app (tool_name:ARCH), where ARCH is the architecture of the user's system, and unpacks it to the new tool directory: ~/.local/bin/dropshell-tool/<tool_name>/
        - sets the PATH to include the tool directory, by modifying the ~/.bashrc_dropshell_tool script
        - adds an entry for autocompletion for the tool to the ~/.bashrc_dropshell_tool script, where autocomplete just runs <tool_name> autocomplete <args>
        - creates a ~/.config/dropshell-tool/tool_name.json file, which contains the tool's name, version (by running <tool_name> version), hash from tools.dropshell.app, and architecture
        - reads the json file from the tgz called dropshell-tool-config.json:
            - for each alias in the aliases array:
                - check if another command is using the alias, and continue only if not
                - check that the alias is not already in the ~/.bashrc_dropshell_tool script and continue only if not
                - add an entry to the ~/.bashrc_dropshell_tool script to run the tool via the alias
            - if the json file has a setup_script entry, run the script named in the entry in the tool directory - using sudo if the entry has sudo set to true.

    dropshell-tool publish <tool_name:ARCH> <folder>
        - checks that dropshell-tool-config.json exists in the folder, and is valid (see above)
        - creates a tgz archive of the folder, and uploads it to tools.dropshell.app's simple object storage server.
        - prints the URL and hash of the uploaded archive
        - uses the token ~/.config/tools.dropshell.app/write_token.txt, or prompts the user for a token if it is not found and writes it to the file

    dropshell-tool update <tool_name>
        - compares the hash from the ~/.config/dropshell-tool/tool_name.json file with the hash from tools.dropshell.app (tool_name:ARCH), and continues only if they are different
        - checks the version from the ~/.config/dropshell-tool/tool_name.json file with the version from tools.dropshell.app (tool_name:ARCH), and continues only if the remote version is newer (installed is older)
        - installs the tool as per the install command

    dropshell-tool update all
        - runs update on all installed tools

    dropshell-tool autocomplete <args>
        - shows autocomplete for dropshell-tool, and then exits
        - the tool list to choose from when calling install is hard coded in the autocomplete function

    dropshell-tool version
        - prints the version of dropshell-tool

    dropshell-tool create <tool_name> <directory_name>
        - creates a new tool source directory in relative path <directory_name> if it doesn't exist
        - creates a dropshell-tool-config.json file in the tool source directory if it doesn't exist, with the following entries:
            - aliases: an array of aliases for the tool
            - setup_script: the name of the setup script to run (setup_script.sh)
        - creates a setup_script.sh file in the tool source directory if it doesn't exist, that just prints a completion message and exits

    dropshell-tool help
        - shows this help message


*/
#include "version.hpp"
#include "BashrcEditor.hpp"
#include "DropshellScriptManager.hpp"
#include "GetbinClient.hpp"
#include "ArchiveManager.hpp"
#include <iostream>
#include <string>
#include <vector>
#include <filesystem>
#include <fstream>
#include <nlohmann/json.hpp>

namespace {
using json = nlohmann::json;

std::string get_arch() {
#if defined(__x86_64__) || defined(_M_X64)
    return "x86_64";
#elif defined(__aarch64__)
    return "aarch64";
#else
    return "unknown";
#endif
}

std::string get_home() {
    const char* home = getenv("HOME");
    return home ? std::string(home) : std::string("");
}

int install_tool(int argc, char* argv[]) {
    if (argc < 3) {
        std::cerr << "Usage: dropshell-tool install <tool_name>" << std::endl;
        return 1;
    }
    std::string toolName = argv[2];
    std::string arch = get_arch();
    std::string home = get_home();
    std::filesystem::path configDir = std::filesystem::path(home) / ".config/dropshell-tool";
    std::filesystem::path binDir = std::filesystem::path(home) / ".local/bin/dropshell-tool" / toolName;
    std::filesystem::path archivePath = configDir / (toolName + ".tgz");
    std::filesystem::create_directories(configDir);
    std::filesystem::create_directories(binDir);
    dropshelltool::BashrcEditor bashrcEditor;
    bashrcEditor.addSourceLine();
    DropshellScriptManager scriptManager;
    scriptManager.removeToolEntry(toolName);
    if (std::filesystem::exists(binDir)) std::filesystem::remove_all(binDir);
    GetbinClient getbin;
    std::cout << "Downloading " << toolName << ":" << arch << "..." << std::endl;
    if (!getbin.download(toolName, arch, archivePath.string())) {
        std::cerr << "Failed to download tool archive." << std::endl;
        return 1;
    }
    ArchiveManager archiver;
    if (!archiver.unpack(archivePath.string(), binDir.string())) {
        std::cerr << "Failed to unpack tool archive." << std::endl;
        return 1;
    }
    scriptManager.addToolEntry(toolName, binDir.string());
    scriptManager.addAutocomplete(toolName);
    std::string hash;
    getbin.getHash(toolName, arch, hash);
    std::string version;
    std::string toolPath = binDir.string() + "/" + toolName;
    FILE* fp = popen((toolPath + " version").c_str(), "r");
    if (fp) {
        char buf[128];
        if (fgets(buf, sizeof(buf), fp)) version = std::string(buf);
        pclose(fp);
    }
    json toolInfo = {
        {"name", toolName},
        {"version", version},
        {"hash", hash},
        {"arch", arch}
    };
    std::ofstream toolInfoFile(configDir / (toolName + ".json"));
    toolInfoFile << toolInfo.dump(2);
    toolInfoFile.close();
    std::string configJson;
    if (archiver.readConfigJson(archivePath.string(), configJson)) {
        try {
            auto config = json::parse(configJson);
            if (config.contains("aliases")) {
                for (const auto& alias : config["aliases"]) {
                    std::string aliasStr = alias.get<std::string>();
                    if (!scriptManager.hasAlias(aliasStr)) {
                        scriptManager.addAlias(aliasStr, toolName);
                    }
                }
            }
            if (config.contains("setup_script")) {
                std::string setupScript = config["setup_script"].get<std::string>();
                bool useSudo = config.value("sudo", false);
                std::string setupPath = binDir.string() + "/" + setupScript;
                std::string cmd = (useSudo ? "sudo " : "") + setupPath;
                std::system(cmd.c_str());
            }
        } catch (...) {
            std::cerr << "Warning: failed to parse dropshell-tool-config.json" << std::endl;
        }
    }
    std::cout << "Installed " << toolName << " successfully." << std::endl;
    return 0;
}

int publish_tool(int argc, char* argv[]) {
    if (argc < 4) {
        std::cerr << "Usage: dropshell-tool publish <tool_name:ARCH> <folder>" << std::endl;
        return 1;
    }
    std::string labeltag = argv[2];
    std::string folder = argv[3];
    std::string home = get_home();
    std::filesystem::path configPath = std::filesystem::path(folder) / "dropshell-tool-config.json";
    if (!std::filesystem::exists(configPath)) {
        std::cerr << "dropshell-tool-config.json not found in " << folder << std::endl;
        return 1;
    }
    std::filesystem::path archivePath = std::filesystem::path(home) / ".tmp" / (labeltag + ".tgz");
    std::filesystem::create_directories(archivePath.parent_path());

    ArchiveManager archiver;
    if (!archiver.pack(folder, archivePath.string())) {
        std::cerr << "Failed to create archive." << std::endl;
        return 1;
    }
    std::filesystem::path tokenPath = std::filesystem::path(home) / ".config/tools.dropshell.app/write_token.txt";
    std::string token;
    if (std::filesystem::exists(tokenPath)) {
        std::ifstream tfile(tokenPath);
        std::getline(tfile, token);
    } else {
        std::cout << "Enter tools.dropshell.app write token: ";
        std::getline(std::cin, token);
        std::filesystem::create_directories(tokenPath.parent_path());
        std::ofstream tfile(tokenPath);
        tfile << token << std::endl;
    }
    GetbinClient getbin;
    std::string url, hash;
    if (!getbin.upload(archivePath.string(), url, hash, token)) {
        std::cerr << "Failed to upload archive." << std::endl;
        return 1;
    }
    std::cout << "Published! URL: " << url << "\nHash: " << hash << std::endl;
    return 0;
}

int update_tool(int argc, char* argv[]) {
    if (argc < 3) {
        std::cerr << "Usage: dropshell-tool update <tool_name|all>" << std::endl;
        return 1;
    }
    std::string toolName = argv[2];
    std::string home = get_home();
    std::filesystem::path configDir = std::filesystem::path(home) / ".config/dropshell-tool";
    if (toolName == "all") {
        for (const auto& entry : std::filesystem::directory_iterator(configDir)) {
            if (entry.path().extension() == ".json") {
                std::string tname = entry.path().stem();
                char* fakeArgv[] = {argv[0], (char*)"update", (char*)tname.c_str()};
                update_tool(3, fakeArgv);
            }
        }
        return 0;
    }
    std::filesystem::path toolInfoPath = configDir / (toolName + ".json");
    if (!std::filesystem::exists(toolInfoPath)) {
        std::cerr << "Tool not installed: " << toolName << std::endl;
        return 1;
    }
    std::ifstream tfile(toolInfoPath);
    json toolInfo;
    tfile >> toolInfo;
    tfile.close();
    std::string arch = toolInfo.value("arch", get_arch());
    std::string localHash = toolInfo.value("hash", "");
    std::string localVersion = toolInfo.value("version", "");
    GetbinClient getbin;
    std::string remoteHash;
    getbin.getHash(toolName, arch, remoteHash);
    if (remoteHash.empty() || remoteHash == localHash) {
        std::cout << "No update needed for " << toolName << std::endl;
        return 0;
    }
    std::cout << "Updating " << toolName << "..." << std::endl;
    char* fakeArgv[] = {argv[0], (char*)"install", (char*)toolName.c_str()};
    return install_tool(3, fakeArgv);
}

int create_tool(int argc, char* argv[]) {
    if (argc < 4) {
        std::cerr << "Usage: dropshell-tool create <tool_name> <directory_name>" << std::endl;
        return 1;
    }
    std::string toolName = argv[2];
    std::string directoryName = argv[3];
    std::filesystem::path toolDir = std::filesystem::current_path() / directoryName;
    if (!std::filesystem::exists(toolDir)) {
        std::filesystem::create_directories(toolDir);
        std::cout << "Created directory: " << toolDir << std::endl;
    } else {
        std::cout << "Directory already exists: " << toolDir << std::endl;
    }
    std::filesystem::path configPath = toolDir / "dropshell-tool-config.json";
    if (!std::filesystem::exists(configPath)) {
        nlohmann::json config = {
            {"aliases", nlohmann::json::array()},
            {"setup_script", "setup_script.sh"}
        };
        std::ofstream configFile(configPath);
        configFile << config.dump(2);
        configFile.close();
        std::cout << "Created config: " << configPath << std::endl;
    } else {
        std::cout << "Config already exists: " << configPath << std::endl;
    }
    std::filesystem::path setupScriptPath = toolDir / "setup_script.sh";
    if (!std::filesystem::exists(setupScriptPath)) {
        std::ofstream setupFile(setupScriptPath);
        setupFile << "#!/bin/bash\necho 'Setup complete.'\n";
        setupFile.close();
        std::filesystem::permissions(setupScriptPath, std::filesystem::perms::owner_exec | std::filesystem::perms::owner_write | std::filesystem::perms::owner_read);
        std::cout << "Created setup script: " << setupScriptPath << std::endl;
    } else {
        std::cout << "Setup script already exists: " << setupScriptPath << std::endl;
    }
    return 0;
}

} // end anonymous namespace

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cout << "Usage: dropshell-tool <command> [args...]" << std::endl;
        return 1;
    }
    std::string command = argv[1];
    if (command == "install") {
        return install_tool(argc, argv);
    } else if (command == "publish") {
        return publish_tool(argc, argv);
    } else if (command == "update") {
        return update_tool(argc, argv);
    } else if (command == "autocomplete") {
        std::vector<std::string> args(argv + 2, argv + argc);
        if (args.empty()) std::cout << R"(install
publish
update
version
create
help
)";
    } else if (command == "version") {
        std::cout << dropshell::VERSION << std::endl;
    } else if (command == "create") {
        return create_tool(argc, argv);
    } else if (command == "help") {
        std::cout << "Usage: dropshell-tool <command> [args...]" << std::endl;
        std::cout << "Commands:" << std::endl;
        std::cout << "  install <tool_name>" << std::endl;
        std::cout << "  publish <tool_name:ARCH> <folder>" << std::endl;
        std::cout << "  update <tool_name>" << std::endl;
        std::cout << "  version" << std::endl;
    } else {
        std::cout << "Unknown command: " << command << std::endl;
        return 1;
    }
    return 0;
}