#include "runner.h"
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <array>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <libssh/libssh.h>
#include <libssh/sftp.h>
#include <pwd.h>

namespace dropshell {

bool Runner::run(const nlohmann::json& run_json) {
    std::string command;
    std::vector<std::string> args;
    std::string working_dir;
    std::map<std::string, std::string> env;
    bool silent, interactive;
    
    if (!parse_json(run_json, command, args, working_dir, env, silent, interactive)) {
        return false;
    }
    
    int exit_code;
    if (run_json.contains("ssh")) {
        exit_code = execute_ssh(run_json["ssh"], command, args, working_dir, env, silent, interactive);
    } else {
        exit_code = execute_local(command, args, working_dir, env, silent, interactive);
    }
    
    return exit_code == 0;
}

bool Runner::run(const nlohmann::json& run_json, std::string& output) {
    std::string command;
    std::vector<std::string> args;
    std::string working_dir;
    std::map<std::string, std::string> env;
    bool silent, interactive;
    
    if (!parse_json(run_json, command, args, working_dir, env, silent, interactive)) {
        return false;
    }
    
    int exit_code;
    if (run_json.contains("ssh")) {
        exit_code = execute_ssh(run_json["ssh"], command, args, working_dir, env, silent, interactive, &output, true);
    } else {
        exit_code = execute_local(command, args, working_dir, env, silent, interactive, &output, true);
    }
    
    return exit_code == 0;
}

bool Runner::parse_json(
    const nlohmann::json& run_json, 
    std::string& command, 
    std::vector<std::string>& args,
    std::string& working_dir,
    std::map<std::string, std::string>& env,
    bool& silent,
    bool& interactive
) {
    try {
        // Command is required
        if (!run_json.contains("command") || !run_json["command"].is_string()) {
            std::cerr << "Error: 'command' field is required and must be a string" << std::endl;
            return false;
        }
        command = run_json["command"];
        
        // Args are optional
        args.clear();
        if (run_json.contains("args")) {
            if (!run_json["args"].is_array()) {
                std::cerr << "Error: 'args' field must be an array" << std::endl;
                return false;
            }
            for (const auto& arg : run_json["args"]) {
                if (!arg.is_string()) {
                    std::cerr << "Error: All arguments must be strings" << std::endl;
                    return false;
                }
                args.push_back(arg);
            }
        }
        
        // Working directory is optional
        working_dir = "";
        if (run_json.contains("working_directory")) {
            if (!run_json["working_directory"].is_string()) {
                std::cerr << "Error: 'working_directory' field must be a string" << std::endl;
                return false;
            }
            working_dir = run_json["working_directory"];
        }
        
        // Environment variables are optional
        env.clear();
        if (run_json.contains("env")) {
            if (!run_json["env"].is_object()) {
                std::cerr << "Error: 'env' field must be an object" << std::endl;
                return false;
            }
            for (auto it = run_json["env"].begin(); it != run_json["env"].end(); ++it) {
                if (!it.value().is_string()) {
                    std::cerr << "Error: All environment variable values must be strings" << std::endl;
                    return false;
                }
                env[it.key()] = it.value();
            }
        }
        
        // Options are optional
        silent = false;
        interactive = false;
        if (run_json.contains("options")) {
            if (!run_json["options"].is_object()) {
                std::cerr << "Error: 'options' field must be an object" << std::endl;
                return false;
            }
            
            if (run_json["options"].contains("silent")) {
                if (!run_json["options"]["silent"].is_boolean()) {
                    std::cerr << "Error: 'silent' option must be a boolean" << std::endl;
                    return false;
                }
                silent = run_json["options"]["silent"];
            }
            
            if (run_json["options"].contains("interactive")) {
                if (!run_json["options"]["interactive"].is_boolean()) {
                    std::cerr << "Error: 'interactive' option must be a boolean" << std::endl;
                    return false;
                }
                interactive = run_json["options"]["interactive"];
            }
        }
        
        return true;
    } catch (const std::exception& e) {
        std::cerr << "Error parsing JSON: " << e.what() << std::endl;
        return false;
    }
}

int Runner::execute_local(
    const std::string& command,
    const std::vector<std::string>& args,
    const std::string& working_dir,
    const std::map<std::string, std::string>& env,
    bool silent,
    bool interactive,
    std::string* output,
    bool capture_output
) {
    int pipefd[2];
    if (capture_output && pipe(pipefd) == -1) {
        std::cerr << "Error creating pipe: " << strerror(errno) << std::endl;
        return -1;
    }
    
    pid_t pid = fork();
    if (pid == -1) {
        std::cerr << "Fork failed: " << strerror(errno) << std::endl;
        if (capture_output) {
            close(pipefd[0]);
            close(pipefd[1]);
        }
        return -1;
    }
    
    if (pid == 0) {
        // Child process
        
        // Set up output redirection if needed
        if (capture_output) {
            close(pipefd[0]);  // Close read end
            dup2(pipefd[1], STDOUT_FILENO);
            dup2(pipefd[1], STDERR_FILENO);
            close(pipefd[1]);
        } else if (silent) {
            int devnull = open("/dev/null", O_WRONLY);
            if (devnull != -1) {
                dup2(devnull, STDOUT_FILENO);
                dup2(devnull, STDERR_FILENO);
                close(devnull);
            }
        }
        
        // Change to working directory if specified
        if (!working_dir.empty()) {
            if (chdir(working_dir.c_str()) != 0) {
                std::cerr << "Error changing to directory " << working_dir << ": " << strerror(errno) << std::endl;
                exit(1);
            }
        }
        
        // Set environment variables
        for (const auto& [key, value] : env) {
            setenv(key.c_str(), value.c_str(), 1);
        }
        
        // Prepare arguments
        std::vector<char*> c_args;
        c_args.push_back(const_cast<char*>(command.c_str()));
        for (const auto& arg : args) {
            c_args.push_back(const_cast<char*>(arg.c_str()));
        }
        c_args.push_back(nullptr);
        
        // Execute command
        execvp(command.c_str(), c_args.data());
        
        // If exec fails
        std::cerr << "exec failed: " << strerror(errno) << std::endl;
        exit(1);
    } else {
        // Parent process
        if (capture_output) {
            close(pipefd[1]);  // Close write end
            
            std::array<char, 4096> buffer;
            std::ostringstream oss;
            
            ssize_t bytes_read;
            while ((bytes_read = read(pipefd[0], buffer.data(), buffer.size() - 1)) > 0) {
                buffer[bytes_read] = '\0';
                oss << buffer.data();
                
                if (!silent) {
                    std::cout << buffer.data();
                }
            }
            
            close(pipefd[0]);
            
            if (output) {
                *output = oss.str();
            }
        }
        
        int status;
        waitpid(pid, &status, 0);
        
        if (WIFEXITED(status)) {
            return WEXITSTATUS(status);
        } else {
            return -1;
        }
    }
}

std::string find_ssh_key_for_user() {
    const char* home_dir = getenv("HOME");
    if (!home_dir) {
        struct passwd* pw = getpwuid(getuid());
        if (pw) {
            home_dir = pw->pw_dir;
        }
    }
    
    if (!home_dir) {
        return "";
    }
    
    // Common SSH key locations
    std::vector<std::string> key_paths = {
        std::string(home_dir) + "/.ssh/id_rsa",
        std::string(home_dir) + "/.ssh/id_ed25519",
        std::string(home_dir) + "/.ssh/id_ecdsa",
        std::string(home_dir) + "/.ssh/id_dsa"
    };
    
    for (const auto& path : key_paths) {
        if (access(path.c_str(), F_OK) == 0) {
            return path;
        }
    }
    
    return "";
}

int Runner::execute_ssh(
    const nlohmann::json& ssh_config,
    const std::string& command,
    const std::vector<std::string>& args,
    const std::string& working_dir,
    const std::map<std::string, std::string>& env,
    bool silent,
    bool interactive,
    std::string* output,
    bool capture_output
) {
    if (!ssh_config.contains("host") || !ssh_config["host"].is_string()) {
        std::cerr << "Error: SSH configuration requires 'host' field" << std::endl;
        return -1;
    }
    std::string host = ssh_config["host"];
    
    int port = 22;
    if (ssh_config.contains("port")) {
        if (ssh_config["port"].is_number_integer()) {
            port = ssh_config["port"];
        } else {
            std::cerr << "Error: SSH 'port' must be an integer" << std::endl;
            return -1;
        }
    }
    
    std::string user = "";
    if (ssh_config.contains("user") && ssh_config["user"].is_string()) {
        user = ssh_config["user"];
    } else {
        // Get current username as default
        char username[256];
        if (getlogin_r(username, sizeof(username)) == 0) {
            user = username;
        } else {
            struct passwd* pw = getpwuid(getuid());
            if (pw) {
                user = pw->pw_name;
            }
        }
    }
    
    std::string key_path = "";
    if (ssh_config.contains("key") && ssh_config["key"].is_string()) {
        std::string key = ssh_config["key"];
        if (key == "auto") {
            key_path = find_ssh_key_for_user();
            if (key_path.empty()) {
                std::cerr << "Error: Could not find SSH key automatically" << std::endl;
                return -1;
            }
        } else {
            key_path = key;
        }
    } else {
        key_path = find_ssh_key_for_user();
    }
    
    // Initialize SSH session
    ssh_session session = ssh_new();
    if (session == nullptr) {
        std::cerr << "Error: Failed to create SSH session" << std::endl;
        return -1;
    }
    
    // Set SSH options
    ssh_options_set(session, SSH_OPTIONS_HOST, host.c_str());
    ssh_options_set(session, SSH_OPTIONS_PORT, &port);
    ssh_options_set(session, SSH_OPTIONS_USER, user.c_str());
    
    // Connect to server
    int rc = ssh_connect(session);
    if (rc != SSH_OK) {
        std::cerr << "Error connecting to " << host << ": " << ssh_get_error(session) << std::endl;
        ssh_free(session);
        return -1;
    }
    
    // Authenticate with key
    if (!key_path.empty()) {
        rc = ssh_userauth_publickey_auto(session, nullptr, key_path.empty() ? nullptr : key_path.c_str());
        if (rc != SSH_AUTH_SUCCESS) {
            std::cerr << "Error authenticating with key: " << ssh_get_error(session) << std::endl;
            ssh_disconnect(session);
            ssh_free(session);
            return -1;
        }
    } else {
        // Try default authentication methods
        rc = ssh_userauth_publickey_auto(session, nullptr, nullptr);
        if (rc != SSH_AUTH_SUCCESS) {
            std::cerr << "Error authenticating: " << ssh_get_error(session) << std::endl;
            ssh_disconnect(session);
            ssh_free(session);
            return -1;
        }
    }
    
    // Prepare command
    std::ostringstream cmd_stream;
    
    // Add environment variables
    for (const auto& [key, value] : env) {
        cmd_stream << "export " << key << "=\"" << value << "\"; ";
    }
    
    // Add cd command if working_directory is specified
    if (!working_dir.empty()) {
        cmd_stream << "cd \"" << working_dir << "\" && ";
    }
    
    // Add command and args
    cmd_stream << command;
    for (const auto& arg : args) {
        cmd_stream << " " << arg;
    }
    
    std::string full_command = cmd_stream.str();
    
    int exit_status = -1;
    ssh_channel channel = ssh_channel_new(session);
    if (channel == nullptr) {
        std::cerr << "Error creating SSH channel: " << ssh_get_error(session) << std::endl;
        ssh_disconnect(session);
        ssh_free(session);
        return -1;
    }
    
    rc = ssh_channel_open_session(channel);
    if (rc != SSH_OK) {
        std::cerr << "Error opening SSH session: " << ssh_get_error(session) << std::endl;
        ssh_channel_free(channel);
        ssh_disconnect(session);
        ssh_free(session);
        return -1;
    }
    
    if (interactive) {
        // Request a pseudo-terminal for interactive commands with specific term type
        const char* term_type = "xterm-256color";
        rc = ssh_channel_request_pty_size(channel, term_type, 80, 24);
        if (rc != SSH_OK) {
            // Fallback to basic PTY request
            std::cerr << "Warning: Could not set PTY with size, falling back to basic PTY" << std::endl;
            rc = ssh_channel_request_pty(channel);
            if (rc != SSH_OK) {
                std::cerr << "Error requesting PTY: " << ssh_get_error(session) << std::endl;
                ssh_channel_close(channel);
                ssh_channel_free(channel);
                ssh_disconnect(session);
                ssh_free(session);
                return -1;
            }
        }
    }
    
    // Execute the command
    rc = ssh_channel_request_exec(channel, full_command.c_str());
    if (rc != SSH_OK) {
        std::cerr << "Error executing command: " << ssh_get_error(session) << std::endl;
        ssh_channel_close(channel);
        ssh_channel_free(channel);
        ssh_disconnect(session);
        ssh_free(session);
        return -1;
    }
    
    // Read command output
    char buffer[4096];
    int nbytes;
    std::ostringstream oss;
    
    // Read from stdout
    while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
        if (capture_output) {
            oss.write(buffer, nbytes);
        }
        
        if (!silent) {
            std::cout.write(buffer, nbytes);
        }
    }
    
    // Read from stderr if needed
    while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 1)) > 0) {
        if (capture_output) {
            oss.write(buffer, nbytes);
        }
        
        if (!silent) {
            std::cerr.write(buffer, nbytes);
        }
    }
    
    if (capture_output && output) {
        *output = oss.str();
    }
    
    // Get exit status
    ssh_channel_send_eof(channel);
    exit_status = ssh_channel_get_exit_status(channel);
    
    // Clean up
    ssh_channel_close(channel);
    ssh_channel_free(channel);
    ssh_disconnect(session);
    ssh_free(session);
    
    return exit_status;
}

} // namespace dropshell