package ru.kirillius.pf.sdn.External.API;

import lombok.*;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import ru.kirillius.json.JSONProperty;
import ru.kirillius.json.JSONSerializable;
import ru.kirillius.utils.logging.SystemLogger;

import java.io.Closeable;
import java.io.IOException;
import java.util.Arrays;
import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;

import static net.schmizz.sshj.common.IOUtils.readFully;

/**
 * Executes commands either locally or over SSH based on the provided configuration.
 */
public class ShellExecutor implements Closeable {
    private final static String CTX = ShellExecutor.class.getSimpleName();
    private final Config config;
    private SSHClient sshClient;

    /**
     * Creates an executor with the supplied connection configuration.
     */
    public ShellExecutor(Config config) {
        this.config = config;
    }

    /**
     * Executes the given command, either locally or through SSH, returning the captured stdout.
     */
    public String executeCommand(String[] command) {
        var buffer = new StringJoiner(" ");
        Arrays.stream(command).forEach(e -> buffer.add('"' + e + '"'));

        if (config.useSSH) {

            try {
                if (sshClient == null) {
                    sshClient = new SSHClient();
                    sshClient.addHostKeyVerifier(new PromiscuousVerifier());

                    sshClient.connect(config.host, config.port);
                    sshClient.authPassword(config.username, config.password);
                }

                try (var session = sshClient.startSession()) {
                    var process = session.exec(buffer.toString());
                    var output = readFully(process.getInputStream()).toString();
                    process.join(10, TimeUnit.SECONDS);
                    return output;
                }
            } catch (IOException e) {
                SystemLogger.error("Failed to execute remote command " + buffer + " via ssh on host " + config.host, CTX, e);
                return null;
            }
        } else {

            try {
                var runtime = Runtime.getRuntime();
                var process = runtime.exec(command);
                var output = readFully(process.getInputStream()).toString();
                process.waitFor();
                return output;
            } catch (IOException | InterruptedException e) {
                SystemLogger.error("Failed to execute local shell command " + buffer, CTX, e);
                return null;
            }
        }
    }

    /**
     * Closes the underlying SSH client when one was created.
     */
    @Override
    public void close() throws IOException {


        if (sshClient != null) {
            sshClient.disconnect();
            sshClient.close();
            sshClient = null;
        }


    }

    /**
     * Connection and authentication options for shell command execution.
     */
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    @JSONSerializable
    public static class Config {
        @Getter
        @Setter
        @JSONProperty
        private volatile boolean useSSH = false;
        @Getter
        @Setter
        @JSONProperty
        private volatile String host = "127.0.0.1";
        @Getter
        @Setter
        @JSONProperty
        private volatile int port = 22;
        @Getter
        @Setter
        @JSONProperty
        private volatile String username = "root";
        @Getter
        @Setter
        @JSONProperty
        private volatile String password = "securepassword";

        /**
         * Returns a human-readable description of the configured shell target.
         */
        @Override
        public String toString() {
            var builder = new StringBuilder();
            builder.append("Shell in ");
            if (useSSH) {
                builder.append("ssh ").append(username).append("@").append(host).append(":").append(port);
            } else {
                builder.append("localhost");
            }
            return builder.toString();
        }
    }
}
