package cn.reghao.bnt.deployer.util;

import cn.reghao.bnt.deployer.model.RemoteHost;
import cn.reghao.bnt.deployer.model.UserInfoImpl;
import cn.reghao.jutil.jdk.shell.ShellResult;
import cn.reghao.jutil.jdk.io.TextFile;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author reghao
 * @date 2024-02-19 11:38:54
 */
@Slf4j
public class Sftp {
    private final TextFile textFile = new TextFile();
    private final String bash = "/usr/bin/bash";

    public Session getSession(RemoteHost remoteHost) throws Exception {
        String host = remoteHost.getHost();
        int port = remoteHost.getPort();
        String username = remoteHost.getUsername();
        String password = remoteHost.getPassword();
        String prikeyPath = remoteHost.getPrikeyPath();

        JSch jsch = new JSch();
        Session session;
        if (port <= 0) {
            //连接服务器，采用默认端口
            session = jsch.getSession(username, host);
        } else {
            //采用指定的端口连接服务器
            session = jsch.getSession(username, host, port);
        }

        //如果服务器连接不上，则抛出异常
        if (session == null) {
            throw new Exception("session is null");
        }

        if (password != null) {
            //设置登陆主机的密码
            session.setPassword(password);
            //设置第一次登陆的时候提示，可选值：(ask | yes | no)
            session.setConfig("StrictHostKeyChecking", "no");
        } else if (prikeyPath != null) {
            session.setConfig("PreferredAuthentications", "publickey");
            session.setConfig("userauth.gssapi-with-mic", "no");
            session.setConfig("StrictHostKeyChecking", "ask");
            session.setUserInfo(new UserInfoImpl(""));
            jsch.addIdentity(prikeyPath);

            session.setConfig("UseDNS", "no");
            session.setConfig("kex", "diffie-hellman-group1-sha1,"
                    + "diffie-hellman-group-exchange-sha1,"
                    + "diffie-hellman-group-exchange-sha256");
        } else {
            throw new Exception("password and private key not exist either");
        }

        session.connect(30_000);
        return session;
    }

    public void upload(Session session, String local, String destDir) throws JSchException, SftpException, IOException {
        // 创建 sftp 通信通道
        Channel channel = session.openChannel("sftp");
        channel.connect(5_000);
        ChannelSftp sftp = (ChannelSftp) channel;
        sftp.cd(destDir);
        File localFile = new File(local);
        if (localFile.isFile()) {
            putFile(localFile, destDir, sftp);
        } else {
            for (File file : Objects.requireNonNull(localFile.listFiles())) {
                if (file.isFile()) {
                    putFile(file, destDir, sftp);
                }
            }
        }

        if (channel.isConnected()) {
            channel.disconnect();
        }
    }

    private void putFile(File localFile, String destDir, ChannelSftp sftp) throws IOException, SftpException {
        String filename = localFile.getName();
        String remoteFilePath = String.format("%s/%s", destDir, filename);
        OutputStream outstream = sftp.put(remoteFilePath);

        InputStream instream = new FileInputStream(localFile);
        byte[] bytes = new byte[1024];
        int n;
        while ((n = instream.read(bytes)) != -1) {
            outstream.write(bytes, 0, n);
        }

        outstream.flush();
        outstream.close();
        instream.close();
    }

    /**
     * 在远程机器上创建目录(等价于 mkdir -p)
     *
     * @param
     * @return
     * @date 2024-02-20 09:20:11
     */
    public boolean mkdir(Session session, String remotePath) throws SftpException, JSchException {
        boolean exist = true;
        // 创建 sftp 通信通道
        Channel channel = session.openChannel("sftp");
        channel.connect(5_000);
        ChannelSftp sftp = (ChannelSftp) channel;

        List<String> list = new ArrayList<>();
        String[] arr = remotePath.split("/");
        for (int i = 0; i < arr.length; i++) {
            String path = "/" + arr[i];
            if (i-1 > 0) {
                list.add(list.get(i-1) + path);
            } else {
                list.add(path);
            }
        }

        for (String path : list) {
            try {
                sftp.stat(path);
            } catch (SftpException e) {
                exist = false;
                sftp.mkdir(path);
            }
        }

        return exist;
    }

    public ShellResult exec(Session session, String command) throws JSchException, IOException {
        StringBuilder sb = new StringBuilder();
        // 创建 exec 通信通道
        ChannelExec channel = (ChannelExec) session.openChannel("exec");
        channel.setCommand(command);
        channel.setInputStream(null);
        channel.setErrStream(System.err);
        InputStream input = channel.getInputStream();
        InputStream error = channel.getErrStream();

        // timeout 设置为 10 分钟
        channel.connect(600_000);
        /*byte[] tmp = new byte[1024];
        int i = 0;
        while (true) {
            while (input.available() > 0) {
                i = input.read(tmp, 0, 1024);
                if (i < 0) {
                    break;
                }
            }

            if (channel.isClosed()) {
                if (input.available() > 0) {
                    continue;
                }

                log.info("channel closed with status: {}", channel.getExitStatus());
                break;
            }

            try {
                Thread.sleep(1000);
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
        sb.append(new String(tmp, 0, i));
        */
        // stdin
        BufferedReader inputReader = new BufferedReader(new InputStreamReader(input));
        String line;
        while((line = inputReader.readLine()) != null) {
            sb.append(line).append(System.lineSeparator());
        }

        // stderr
        StringBuilder sb1 = new StringBuilder();
        BufferedReader errorReader = new BufferedReader(new InputStreamReader(error));
        String line1;
        while((line1 = errorReader.readLine()) != null) {
            sb1.append(line1).append(System.lineSeparator());
        }

        int statusCode = channel.getExitStatus();
        ShellResult shellResult = new ShellResult(statusCode);
        if (statusCode == 0) {
            shellResult.setResult(sb.toString());
        } else {
            shellResult.setResult(sb1.toString());
        }

        if (channel.isConnected()) {
            channel.disconnect();
        }

        return shellResult;
    }

    public List<RemoteHost> getRemoteHost(String filePath) {
        List<String> list = textFile.read(filePath);
        List<RemoteHost> remoteHosts = new ArrayList<>();
        for (int i = 1; i < list.size(); i++) {
            // 从 csv 文件的第二行开始读, 第一行为注释
            String line = list.get(i);
            String[] arr = line.split(",");
            if (arr.length == 4) {
                String host = arr[0];
                int port = Integer.parseInt(arr[1]);
                String username = arr[2];
                String password = arr[3];
                remoteHosts.add(new RemoteHost(host, port, username, password, null));
            } else if (arr.length == 5) {
                String host = arr[0];
                int port = Integer.parseInt(arr[1]);
                String username = arr[2];
                String prikeyPath = arr[4];
                remoteHosts.add(new RemoteHost(host, port, username, null, prikeyPath));
            }
        }

        return remoteHosts;
    }

    public void deploy(String localDir, String remoteDir, RemoteHost remoteHost) throws Exception {
        Session session = getSession(remoteHost);
        boolean exist = mkdir(session, remoteDir);
        if (exist) {
            String command = String.format("cd %s && %s shutdown.sh", remoteDir, bash);
            ShellResult shellResult = exec(session, command);
            if (!shellResult.isSuccess()) {
                log.info("shutdown application failed\nexitCode: {}\nresult:\n{}", shellResult.getExitCode(), shellResult.getResult());
                System.exit(-1);
            } else {
                log.info("shutdown application successfully");
            }
        } else {
            log.info("remote dir {} created", remoteDir);
        }

        upload(session, localDir, remoteDir);
        log.info("files uploaded");

        String command = String.format("cd %s && %s start.sh", remoteDir, bash);
        ShellResult shellResult = exec(session, command);
        if (!shellResult.isSuccess()) {
            log.info("start application failed\nexitCode: {}\nresult:\n{}", shellResult.getExitCode(), shellResult.getResult());
            System.exit(-1);
        } else {
            log.info("start application successfully");
        }

        if (session.isConnected()) {
            session.disconnect();
        }
    }

    public void exec(RemoteHost remoteHost, String command) throws Exception {
        Session session = getSession(remoteHost);
        ShellResult shellResult = exec(session, command);
        log.info("\nexitCode: {}\nresult:\n{}", shellResult.getExitCode(), shellResult.getResult());

        if (session.isConnected()) {
            session.disconnect();
        }
    }

    public static void main(String[] args) throws Exception {
        String host = "192.168.0.180";
        int port = 22;
        String username = "root";
        String password = "gsh";
        RemoteHost remoteHost = new RemoteHost(host, port, username, password, null);

        String command = "docker ps -a";
        Sftp sftp = new Sftp();
        sftp.exec(remoteHost, command);
    }
}
