#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <cstring>
#include <sstream>
#include <filesystem>
#include "version.hpp"
#include "b64ed.hpp"

// Recursively decode and print if nested bb64 command is found
void recursive_print(const std::string &decoded)
{
    std::cout << std::string(80, '-') << std::endl;
    std::cout << decoded << std::endl;
    std::cout << std::string(80, '-') << std::endl;

    size_t pos = decoded.find("bb64 ");
    if (pos != std::string::npos)
    {
        std::istringstream iss(decoded.substr(pos));
        std::string cmd, arg;
        iss >> cmd >> arg;
        if (cmd == "bb64" && !arg.empty())
        {
            std::string nested = base64_decode(arg);
            std::cout << " ";
            std::cout << "nested: " << nested << std::endl;
            recursive_print(nested);
        }
    }
}

constexpr unsigned int hash(const char *s, int off = 0)
{
    return !s[off] ? 5381 : (hash(s, off + 1) * 33) ^ s[off];
}

std::string tidy(const std::string &str)
{
    std::string result;
    bool in_whitespace = false;
    for (char c : str)
    {
        // Remove non-printable characters except for whitespace (space, tab, newline, carriage return)
        if ((static_cast<unsigned char>(c) < 32 && c != ' ' && c != '\t' && c != '\n' && c != '\r') || static_cast<unsigned char>(c) == 127)
        {
            continue;
        }
        if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
        {
            if (!in_whitespace)
            {
                result += ' ';
                in_whitespace = true;
            }
        }
        else
        {
            result += c;
            in_whitespace = false;
        }
    }
    // Remove leading whitespace
    size_t start = result.find_first_not_of(' ');
    if (start == std::string::npos)
        return "";
    // Remove trailing whitespace
    size_t end = result.find_last_not_of(' ');
    return result.substr(start, end - start + 1);
}
    

std::string get_arch()
{
    // determine the architecture of the system
    std::string arch;
#ifdef __aarch64__
    arch = "arm64";
#elif __x86_64__
    arch = "amd64";
#endif
    return arch;
}

int update_bb64()
{
    // determine path to this executable
    std::filesystem::path bb64_path = std::filesystem::canonical("/proc/self/exe");
    std::filesystem::path parent_path = bb64_path.parent_path();

    // determine the architecture of the system
    std::string arch = get_arch();

    std::string url = "https://gitea.jde.nz/public/bb64/releases/download/latest/bb64." + arch;

    // download new version, preserve permissions and ownership
    std::string bash_script;
    bash_script += "docker run --rm -v "+parent_path.string()+":/target";
    bash_script += " gitea.jde.nz/public/debian-curl:latest";
    bash_script += " sh -c \"";
    bash_script += " curl -fsSL " + url + " -o /target/bb64_temp &&";
    bash_script += " chmod --reference=/target/bb64 /target/bb64_temp &&";
    bash_script += " chown --reference=/target/bb64 /target/bb64_temp &&";
    bash_script += " mv /target/bb64_temp /target/bb64";
    bash_script += "\"";

    std::cout << "Updating " << bb64_path << " to the latest " << arch << " version." << std::endl;

    // std::cout << "bash_script: " << std::endl
    //           << bash_script << std::endl;

    // run the bash script
    execlp("bash", "bash", "-c", bash_script.c_str(), (char *)nullptr);
    std::cerr << "Failed to execute command." << std::endl;
    return -1;
}

int decode_and_run(const std::string &encoded)
{
    // Default: decode and run
    std::string decoded = base64_decode(encoded);
    if (decoded.empty())
    {
        std::cerr << "Failed to decode base64 command." << std::endl;
        return -1;
    }
    // Replace current process with bash -c "decoded"
    execlp("bash", "bash", "-c", decoded.c_str(), (char *)nullptr);
    // If execlp returns, there was an error
    std::cerr << "Failed to execute command." << std::endl;
    return -1;
}

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        std::cerr << "bb64 version " << VERSION << ", by J842." << std::endl;
        // heredoc for instructions
        std::cerr << R"(    

Usage:
    bb64 BASE64COMMAND          Decodes and runs the command
    bb64 -[i|d] BASE64COMMAND   Displays the decoded command
    
    bb64 -e COMMAND             Encodes the command and prints the base64 encoded result
    bb64 -e                     Encodes the command provided on stdin and prints the result

    bb64 -u                     Updates bb64 to the latest version (uses docker)

    bb64 -v                     Prints the version number
    bb64 version                Prints the version number

)" << std::endl;
        return -1;
    }

    std::string mode = argv[1];

    if (argc == 2)
    {
        if (mode == "-u")
            return update_bb64();
        else if (mode == "-v" || mode == "version")
        {
            std::cout << VERSION << std::endl;
            return 0;
        }
        else if (mode == "-e")
        {
            std::ostringstream oss;
            while (std::cin)
            {
                std::string line;
                std::getline(std::cin, line);
                oss << line << std::endl;
            }
            std::string tidier = tidy(oss.str());
            std::cout << base64_encode(tidier) << std::endl;
            return 0;
        }
        else
            return decode_and_run(mode);
    }

    std::ostringstream oss;
    std::string tidier;

    switch (hash(mode.c_str()))
    {
    case hash("-i"):
    case hash("-d"):
        std::cout << "Decoding command..." << std::endl
                  << std::endl;
        recursive_print(base64_decode(argv[2]));
        break;
    case hash("-e"):
        for (int i = 2; i < argc; ++i)
            oss << (i > 2 ? " " : "") << argv[i];
        tidier = tidy(oss.str());
        std::cout << base64_encode(tidier) << std::endl;
        break;
    default:
        std::cerr << "Invalid mode: " << mode << std::endl;
        return -1;
    };
}