package cd.casic.ci.process.ssh;


import cd.casic.ci.process.constant.CommandConstant;
import cd.casic.ci.process.util.ChannelShellUtil;
import cd.casic.module.machine.dal.dataobject.MachineInfoDO;
import cd.casic.module.machine.utils.CryptogramUtil;
import cn.hutool.extra.ssh.JschUtil;
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName ops
 * @Description ssh 的命令操作的集合 ,这里是做个统一,其他pipleline 和web ternminal 的实现方式不同
 * @Author mianbin
 * @Date 2022/6/22 15:43
 * @Version 1.0
 **/
@Slf4j
public class SshCommand implements SshClient {

    private static final int BUFF_SIZE = 1024 * 8; // 8KB

    /**
     * jsch session
     */
    Session session;


    /**
     * 基于用户名密码认证的构造器
     *
     * @param username 用户名
     * @param password 密码
     * @param host     主机ip
     * @param port     ssh端口号
     * @throws JSchException
     */

    public SshCommand(String username, String password, String host, int port) throws JSchException {
        this.session = JschUtil.createSession(host, port, username, password);
        this.session.setConfig("PreferredAuthentications", "password");
        this.session.setConfig("StrictHostKeyChecking", "no");
        this.session.setPassword(password);
        this.session.connect();
    }

    /**
     * 基于用户名密码认证的构造器
     * Machine实体类
     *
     * @throws JSchException
     */
    public SshCommand(MachineInfoDO machine) throws JSchException {
        // 根据用户名，主机ip，端口获取一个Session对象
        String decrypt = CryptogramUtil.doDecrypt(machine.getPassword());
        this.session = JschUtil.createSession(machine.getHostIp(), machine.getSshPort(), machine.getUsername(), decrypt);
        this.session.setConfig("PreferredAuthentications", "password");
        this.session.setConfig("StrictHostKeyChecking", "no");
        // 通过Session建立链接
        this.session.connect();
    }

    @Override
    public void disconnect() {
        if(session!=null) {
            session.disconnect();
        }
    }

    @Override
    public int exec(String command, ExecCallback execCallback) throws IOException {

        ChannelShell channel = null;
        try {
            channel = (ChannelShell) session.openChannel("shell");
            channel.connect();
        } catch (JSchException e) {
            e.printStackTrace();
        }

        InputStream inputStream = channel.getInputStream();
        OutputStream outputStream = channel.getOutputStream();
        PrintWriter printWriter = new PrintWriter(outputStream);

        printWriter.println(command);

        printWriter.println("exit");
        printWriter.flush();
        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));

        String msg = null;
        while ((msg = in.readLine()) != null) {
            execCallback.callback(msg);
        }
        int result = channel.getExitStatus();

        in.close();

        return result;
    }


    @Override
    public int exec(List<String> commands, ExecCallback execCallback) throws IOException {

        ChannelShell channel = null;
        try {
            channel = (ChannelShell) session.openChannel("shell");
            channel.connect();
        } catch (JSchException e) {
            e.printStackTrace();
        }

        assert channel != null;
        ChannelShellUtil.setDefault(channel);
        InputStream inputStream = channel.getInputStream();
        OutputStream outputStream = channel.getOutputStream();
        InputStreamReader isr = new InputStreamReader(inputStream);
        BufferedReader br = new BufferedReader(isr, BUFF_SIZE);
        PrintStream commander = new PrintStream(outputStream, true);

        for (String command : commands) {
            commander.append(command).append(CommandConstant.ENTER);
        }
        commander.append("exit" + CommandConstant.ENTER);

        try {
            char[] buff = new char[BUFF_SIZE];
            int read;
            while ((read = br.read(buff)) != -1) {
                String output = String.valueOf(buff, 0, read);
                execCallback.callback( output + CommandConstant.ENTER);
                TimeUnit.MILLISECONDS.sleep(10L);
                // 检测输出，如果满足跳出条件则退出循环
                if (output.contains("# exit")||output.contains("$ exit")) {
                    commander.append("exit" + CommandConstant.ENTER);
                    TimeUnit.MILLISECONDS.sleep(1000L);
                    break;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        inputStream.close();
        outputStream.close();
        channel.disconnect();
        int result = channel.getExitStatus();
        commander.close();
        isr.close();
        br.close();

        return result;
    }

    @Override
    public int execNew(List<String> commands, String sudoPassword, ExecCallback execCallback) throws IOException {
        ChannelShell channel = null;
        try {
            channel = (ChannelShell) session.openChannel("shell");
            channel.connect();
        } catch (JSchException e) {
            e.printStackTrace();
            throw new IOException("打开shell通道失败", e);
        }
        assert channel != null;
        ChannelShellUtil.setDefault(channel);
        InputStream inputStream = channel.getInputStream();
        OutputStream outputStream = channel.getOutputStream();
        InputStreamReader isr = new InputStreamReader(inputStream);
        BufferedReader br = new BufferedReader(isr, BUFF_SIZE);
        PrintStream commander = new PrintStream(outputStream, true);

        // 逐条命令处理
        for (String cmd : commands) {
            String realCmd;
            if (sudoPassword != null && !sudoPassword.isEmpty()) {
                // 单引号转义，防止脚本内容出错
                String safeCmd = cmd.replace("'", "\\'");
                realCmd = "echo '" + sudoPassword + "' | sudo -S bash -c '" + safeCmd + "'";
                log.info("================管理员权限执行命令{}",realCmd);
                log.info("================原命令{}",safeCmd);
                log.info("");
            } else {
                realCmd = cmd;
            }
            for (String s : realCmd.split("\n")) {
                commander.append(s).append(CommandConstant.ENTER);
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        commander.append("exit").append(CommandConstant.ENTER);

        try {
            char[] buff = new char[BUFF_SIZE];
            int read;
            while ((read = br.read(buff)) != -1) {
                execCallback.callback(String.valueOf(buff, 0, read) + CommandConstant.ENTER);
                TimeUnit.MILLISECONDS.sleep(10L);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        inputStream.close();
        outputStream.close();
        channel.disconnect();
        int result = channel.getExitStatus();
        commander.close();
        isr.close();
        br.close();

        return result;
    }
    @Override
    public int parallelDebugExec(List<String> commands, ExecCallback execCallback) throws IOException {

        ChannelShell channel = null;
        try {
            channel = (ChannelShell) session.openChannel("shell");
            channel.connect();
        } catch (JSchException e) {
            e.printStackTrace();
        }

        assert channel != null;
        ChannelShellUtil.setDefault(channel);
        InputStream inputStream = channel.getInputStream();
        OutputStream outputStream = channel.getOutputStream();
        InputStreamReader isr = new InputStreamReader(inputStream);
        BufferedReader br = new BufferedReader(isr, BUFF_SIZE);
        PrintStream commander = new PrintStream(outputStream, true);

        for (String command : commands) {
            commander.append(command).append(CommandConstant.ENTER);
        }

        try {
            char[] buff = new char[BUFF_SIZE];
            int read;
            while ((read = br.read(buff)) != -1) {
                String msg = String.valueOf(buff, 0, read);
                execCallback.callback(msg + CommandConstant.ENTER);
                TimeUnit.MILLISECONDS.sleep(20L);
                if(msg.endsWith("$ ") || msg.endsWith("# ")) {
                    commander.append("exit").append(CommandConstant.ENTER);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        int result = channel.getExitStatus();

        inputStream.close();
        outputStream.close();
        channel.disconnect();
        commander.close();
        isr.close();
        br.close();

        return result;
    }
}
