package com.educoder.bridge.service;

import com.alibaba.fastjson.JSONObject;
import com.educoder.bridge.model.SSHInfo;
import com.educoder.bridge.model.SSHSession;
import com.educoder.bridge.utils.Base64Util;
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Service
public class JchService {

    private static List<SSHSession> sshSessionQueue = new CopyOnWriteArrayList<>();
    private ExecutorService executorService = Executors.newCachedThreadPool();
    private Logger logger = LoggerFactory.getLogger(getClass());

    com.jcraft.jsch.Logger jschLogger = new com.jcraft.jsch.Logger() {

        @Override
        public boolean isEnabled(int arg0) {
            return true;
        }

        @Override
        public void log(int arg0, String arg1) {
            if (logger.isTraceEnabled()) {
                logger.trace("JSch Log [Level " + arg0 + "]: " + arg1);
            }
        }
    };


    /**
     * 在webSocket连接时，初始化一个ssh连接
     *
     * @param webSocketSession webSocket连接
     */
    public void add(WebSocketSession webSocketSession) {

        SSHSession sshSession = new SSHSession();
        sshSession.setWebSocketSession(webSocketSession);

        sshSessionQueue.add(sshSession);
    }

    /**
     * 处理客户端发过来的数据
     * @param buffer 数据
     * @param webSocketSession webSocket连接
     */
    public void recv(String buffer, WebSocketSession webSocketSession) {

        SSHSession sshSession = null;
        try {
            logger.debug("webSocketSessionID: {}, 信息: {}", webSocketSession.getId(), buffer);
            JSONObject info = JSONObject.parseObject(buffer);
            String tp = info.getString("tp");
            sshSession = findByWebSocketSession(webSocketSession);

            //初始化连接
            if ("init".equals(tp)) {
//              {"tp":"init","data":{"host":"127.0.0.1","port":"41080","username":"root","password":"123123"}}
                SSHInfo sshInfo = info.getObject("data", SSHInfo.class);
                sshSession.setSSHInfo(sshInfo);

                if (sshSession != null) {
                    SSHSession finalSSHSession = sshSession;

                    // 新开一个线程建立连接，连接开启之后以一直监听来自客户端的输入
                    executorService.execute(() -> {
                        connectTossh(finalSSHSession);
                    });
                }
            } else if ("client".equals(tp)) {
                String data = info.getString("data");

                // 将网页输入的数据传送给后端服务器
                if (sshSession != null) {
                    transTossh(sshSession.getOutputStream(), data);
                }
            }
        } catch (Exception e) {
            logger.error("转发命令到ssh出错: {}", e);

            close(sshSession);
        }

    }

    /**
     * 将数据传送给服务端作为SSH的输入
     *
     * @param outputStream
     * @param data
     * @throws IOException
     */
    private void transTossh(OutputStream outputStream, String data) throws IOException {
        if (outputStream != null) {
            outputStream.write(data.getBytes());
            outputStream.flush();
        }
    }

    /**
     * 连接ssh
     *
     * @param sshSession ssh连接需要的信息
     */
    private void connectTossh(SSHSession sshSession){
        Session jschSession = null;
        SSHInfo SSHInfo = sshSession.getSSHInfo();
        try {
            JSch jsch = new JSch();
            JSch.setLogger(jschLogger);

            //启动线程
            java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking", "no");
            jschSession = jsch.getSession(SSHInfo.getUsername(), SSHInfo.getHost(), SSHInfo.getPort());

            jschSession.setConfig(config);
            jschSession.setPassword(SSHInfo.getPassword());
            jschSession.setUserInfo(new UserInfo() {
                @Override
                public String getPassphrase() {
                    return null;
                }

                @Override
                public String getPassword() {
                    return null;
                }

                @Override
                public boolean promptPassword(String s) {
                    return false;
                }

                @Override
                public boolean promptPassphrase(String s) {
                    return false;
                }

                @Override
                public boolean promptYesNo(String s) {
                    return true;
                } // Accept all server keys

                @Override
                public void showMessage(String s) {
                }
            });

            jschSession.connect();
            ChannelShell channel = (ChannelShell) jschSession.openChannel("shell");
            channel.setPtyType("xterm");

            channel.connect();

            sshSession.setChannel(channel);
            InputStream inputStream = channel.getInputStream();
            sshSession.setOutputStream(channel.getOutputStream());

            sshSession.setSSHInfo(SSHInfo);
            logger.debug("主机: {} 连接成功!", SSHInfo.getHost());

            // 循环读取，jsch的输入为服务器执行命令之后的返回数据
            byte[] buf = new byte[1024];
            while (true) {
                int length = inputStream.read(buf);
                if (length < 0) {
                    close(sshSession);
                    throw new Exception("读取出错，数据长度：" + length);
                }
                sendMsg(sshSession.getWebSocketSession(), Arrays.copyOfRange(buf, 0, length));
            }

        } catch (Exception e) {
            logger.error("ssh连接出错, e: {}", e);
        } finally {
            logger.info("连接关闭, {}", SSHInfo.getHost());
            if (jschSession != null) {
                jschSession.disconnect();
            }

            close(sshSession);
        }
    }


    /**
     * 发送数据回websocket
     *
     * @param webSocketSession webSocket连接
     * @param buffer 数据
     * @throws IOException
     */
    public void sendMsg(WebSocketSession webSocketSession, byte[] buffer) throws IOException {
        logger.debug("服务端返回的数据: {}", new String(buffer, "UTF-8"));

        webSocketSession.sendMessage(new TextMessage(Base64Util.encodeBytes(buffer)));
    }

    /**
     * 通过webSocket连接在队列中找到对应的SSH连接
     *
     * @param webSocketSession webSocket连接
     */
    public SSHSession findByWebSocketSession(WebSocketSession webSocketSession) {
        Optional<SSHSession> optional = sshSessionQueue.stream().filter(webscoketObj -> webscoketObj.getWebSocketSession() == webSocketSession).findFirst();
        if (optional.isPresent()) {
            return optional.get();
        }
        return null;
    }

    /**
     * 关闭ssh和websocket连接
     *
     * @param sshSession ssh连接
     */
    private void close(SSHSession sshSession) {
        if (sshSession != null) {
            sshSession.getChannel().disconnect();
            try {
                sshSession.getWebSocketSession().close();
                sshSession.getOutputStream().close();
            } catch (IOException e) {
                logger.error("连接关闭失败！e: {}", e);
            }

            sshSessionQueue.remove(sshSession);
        }
    }

    /**
     * 通过webSocketSession关闭ssh与webSocket连接
     *
     * @param webSocketSession
     */
    public void closeByWebSocket(WebSocketSession webSocketSession) {
        close(findByWebSocketSession(webSocketSession));
    }
}
