package cn.quantgroup.customer.service.ftp;

import cn.quantgroup.customer.exception.BusinessException;
import cn.quantgroup.customer.model.ftp.FileInfo;
import cn.quantgroup.customer.model.ftp.ServerInfo;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

import static com.jcraft.jsch.ChannelSftp.SSH_FX_NO_SUCH_FILE;

@Slf4j
public class AbstractFtpService implements IFtpService {

    private static final int SO_TIME_OUT = 60 * 1000;  // 超时时间
    private static final int TIME_OUT = 15 * 1000;

    @Override
    public Object send(Object request) {
        return null;
    }

    @Override
    public boolean send(FileInfo fileInfo, ServerInfo serverInfo, int retryTime) {
        String path = fileInfo.getFilePath();
        byte[] fileBytes = fileInfo.getFileBytes();
        String fileName = fileInfo.getFileName();

        if (StringUtils.isBlank(fileName)) {
            throw new RuntimeException("文件名不能为空");
        }

        if (fileBytes == null) {
            throw new RuntimeException("文件内容不能为空");
        }
        log.info("AbstractFtpService send fileName={}", fileName);
        JSch jsch = null;

        Session session = null;
        Channel channel = null;
        ChannelSftp channelSftp = null;
        /**
         * 方法返回值
         */
        boolean result = Boolean.FALSE;
        try {
            jsch = new JSch();
            session = jsch.getSession(serverInfo.getUsername(), serverInfo.getHost(), serverInfo.getPort());
            session.setPassword(serverInfo.getPassword().getBytes(Charset.forName("ISO-8859-1")));
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            session.setTimeout(SO_TIME_OUT);
            log.info("连接SFTP服务器host={},port={}", serverInfo.getHost(), serverInfo.getPort());
            session.connect(TIME_OUT);
            channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp)channel;
            log.info("SFTPclient打开channel...");
            InputStream input = null;
            if (!session.isConnected() || !channelSftp.isConnected()) {
                log.error("SFTPchannel已经关闭");
                throw new BusinessException("SFTPchannel已经关闭");
            }
            // 目录不存在则创建
            SftpATTRS exist = null;
            try {
                exist = channelSftp.lstat(serverInfo.getDirectory().concat(path));
            } catch (SftpException e) {
                log.info("路径不存在{},exist={}", serverInfo.getDirectory().concat(path),exist);
            }
            if (exist == null && StringUtils.isNotBlank(path)) {
                path = serverInfo.getDirectory().concat(path);
                createPath(path, channelSftp);
            } else if (StringUtils.isBlank(path)) {
                path = serverInfo.getDirectory();
            } else {
                path = serverInfo.getDirectory().concat(path);
            }
            log.info("SFTP.client.切换到目录={}", path);
            channelSftp.cd(path);
            log.info("SFTP.client.切换到目录成功={}", path);
            input = new ByteArrayInputStream(fileBytes);

            // 重试标上传志位
            boolean retry = Boolean.TRUE;

            int alreadyTryTime = 0;
            log.info("开始上传文件");
            // 发生异常，重试三次
            while (retry && alreadyTryTime <= retryTime) {
                try {
                    channelSftp.put(input, fileName);
                    log.info("SFTP.client.上传成功 目录={} 文件名={}", path, fileName);
                    result = Boolean.TRUE;
                    retry = Boolean.FALSE;
                } catch (Exception e) {
                    log.error("SFTP文件上传失败 filename:{},alreadyTryTime:{},e:{}", fileName, alreadyTryTime, e);
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException interruptedException) {

                    }
                    alreadyTryTime++;
                }
            }

            if (!result) {
                throw new BusinessException("SFTP服务器异常！");
            }

            if (channelSftp != null) {
                channelSftp.disconnect();
            }
            if (channel != null) {
                channel.disconnect();
            }
            if (session != null) {
                session.disconnect();
            }
        } catch (JSchException e) {
            log.error("JSchException fileName={},e={}", fileName, ExceptionUtils.getStackTrace(e));
            throw new BusinessException("SFTP创建连接失败, 信息="+ e);
        } catch (SftpException e) {
            log.error("SftpException fileName={},e={}", fileName, ExceptionUtils.getStackTrace(e));
            throw new BusinessException("SFTP.client.操作失败");
        }
        return result;
    }

    protected void createPath(String path, ChannelSftp channelSftp) throws SftpException {
        String[] folders = path.split("/");
        for (String folder : folders) {
            if (folder.length() > 0) {
                try {
                    channelSftp.cd(folder);
                } catch (SftpException e) {
                    try {
                        channelSftp.mkdir(folder);
                    } catch (SftpException e1) {
                        log.info("创建失败，path:{}, exception:{}", path, ExceptionUtils.getStackTrace(e1));
                    }
                    channelSftp.cd(folder);
                }
            }
        }
    }

    @Override
    public byte[] down(String fullName, ServerInfo serverInfo, int retryTime) throws Exception {
        ByteArrayOutputStream fileOutputStream = new ByteArrayOutputStream();
        JSch jsch;
        Session session = null;
        Channel channel = null;
        ChannelSftp channelSftp = null;
        try {
            jsch = new JSch();
            session = jsch.getSession(serverInfo.getUsername(), serverInfo.getHost(), serverInfo.getPort());
            session.setPassword(serverInfo.getPassword().getBytes(Charset.forName("ISO-8859-1")));
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            session.setTimeout(SO_TIME_OUT);
            log.info("连接 SFTP 服务器 " + serverInfo.getHost() + ":" + serverInfo.getPort());
            session.connect(TIME_OUT);
            channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp)channel;
            log.info("SFTP client 打开 channel ...");
            if (channelSftp == null || session == null || !session.isConnected() || !channelSftp.isConnected()) {
                log.warn("SFTP channel 已经关闭");
                throw new BusinessException("SFTP channel 已经关闭");
            }
            channelSftp.get(fullName, fileOutputStream);
            byte[] data = fileOutputStream.toByteArray();
            if (data == null || data.length < 2) {
                log.warn("SFTP.client.下载回来的文件长度不对！文件名={}", fullName);
                return null;
            }
            log.info("SFTP.client.下载成功 文件名={}", fullName);
            return data;
        } catch (JSchException e) {
            log.warn("SFTP.client.创建连接失败");
            throw new BusinessException("SFTP创建连接失败, 信息=" + e);
        } catch (SftpException e) {
            log.warn("SFTP.client.操作失败, 信息exception=", e);
            if (e.id == SSH_FX_NO_SUCH_FILE) {
                throw new FileNotFoundException("file not found " + fullName);
            }
            throw new BusinessException("SFTP.client.操作失败");
        } finally {
            if (channelSftp != null) {
                channelSftp.disconnect();
            }
            if (channel != null) {
                channel.disconnect();
            }
            if (session != null) {
                session.disconnect();
            }
        }
    }

    @Override
    public List<String> listDirectory(ServerInfo serverInfo) throws Exception {
        JSch jsch;
        Session session = null;
        Channel channel = null;
        ChannelSftp channelSftp = null;
        try {
            jsch = new JSch();
            session = jsch.getSession(serverInfo.getUsername(), serverInfo.getHost(), serverInfo.getPort());
            session.setPassword(serverInfo.getPassword().getBytes(Charset.forName("ISO-8859-1")));
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            session.setTimeout(SO_TIME_OUT);
            log.info("连接 SFTP 服务器 " + serverInfo.getHost() + ":" + serverInfo.getPort());
            session.connect(TIME_OUT);
            channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp)channel;
            log.info("SFTP client 打开 channel ...");
            if (channelSftp == null || session == null || !session.isConnected() || !channelSftp.isConnected()) {
                log.warn("SFTP channel 已经关闭");
                throw new BusinessException("SFTP channel 已经关闭");
            }
            List<String> fileNameList = new ArrayList<>();
            Vector<ChannelSftp.LsEntry> fileNameVector = channelSftp.ls(serverInfo.getDirectory());
            for(ChannelSftp.LsEntry lsEntry : fileNameVector){
                if(lsEntry.getFilename().equals(".") || lsEntry.getFilename().equals("..")){
                    continue;
                }
                fileNameList.add(lsEntry.getFilename());
            }
            return fileNameList;
        } catch (JSchException e) {
            log.warn("SFTP.client.创建连接失败");
            throw new BusinessException("SFTP创建连接失败, 信息={}" + e);
        } catch (SftpException e) {
            log.warn("SFTP.client.操作失败, 信息exception=", e);
            if (e.id == SSH_FX_NO_SUCH_FILE) {
                throw new FileNotFoundException("directory not found " + serverInfo.getDirectory());
            }
            throw new BusinessException("SFTP.client.操作失败");
        } finally {
            if (channelSftp != null) {
                channelSftp.disconnect();
            }
            if (channel != null) {
                channel.disconnect();
            }
            if (session != null) {
                session.disconnect();
            }
        }
    }
}
