package cc.smtweb.system.bpm.util.ftp;

import cc.smtweb.framework.core.util.CommUtil;
import cc.smtweb.framework.core.util.StringUtil;
import cc.smtweb.system.bpm.util.UtilFile;
import cc.smtweb.system.bpm.util.UtilLogger;
import com.jcraft.jsch.*;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 一个sftp的封装类
 */
@SuppressWarnings("UnusedDeclaration")
public class SFtpUtil implements IFtpUtil {
    private static final int LOGIN_REPEAT_TIMES = 1;
    private ChannelSftp sftp;
    private boolean autoFree = true;

    public SFtpUtil() {
        this.sftp = null;
    }

    @Override
    public boolean isAutoFree() {
        return autoFree;
    }

    @Override
    public void setAutoFree(boolean autoFree) {
        this.autoFree = autoFree;
    }

    //登录ftp
    @Override
    public boolean login(IFtpServerConfig ftpConf) {
        try {
            int count = 0;
            while (count < 3) {
                // 设置超时时间为 5s
                count++;
                try {
                    JSch jsch = new JSch();
                    jsch.getSession(ftpConf.getUserName(), ftpConf.getServerIp(), ftpConf.getPort());
                    Session sshSession = jsch.getSession(ftpConf.getUserName(), ftpConf.getServerIp(), ftpConf.getPort());
                    System.out.println("Session created.");
                    sshSession.setPassword(ftpConf.getPassword());
                    Properties sshConfig = new Properties();
                    sshConfig.put("StrictHostKeyChecking", "no");
                    sshSession.setConfig(sshConfig);
                    sshSession.setTimeout(10 * 1000);
                    sshSession.connect();
                    UtilLogger.debug("Session connected[" + count + "/3].");
                    UtilLogger.debug("Opening Channel[" + count + "/3].");
                    Channel channel = sshSession.openChannel("sftp");
                    channel.connect();
                    sftp = (ChannelSftp) channel;
                    UtilLogger.debug("已连接到 " + ftpConf.getServerIp() + "；");
                    return true;
                } catch (Exception e) {
                    UtilLogger.debug("connect fail[" + count + "/3]：" + CommUtil.getOrigMsg(e));
                }
            }
            UtilLogger.debug("sftp登录失败：3次连接超时...");
            return false;
        } catch (Exception e) {
            logError("sftp登录失败", e);
            return false;
        }
    }

    //退出ftp
    @Override
    public void logout() {
        disconnect();
    }

    //是否连接正常
    @Override
    public boolean isConnected() {
        return sftp != null && sftp.isConnected();
        /*try {
            return sftpClient.sendSignal();
        } catch (IOException e) {
            logError("ftp测试连接失败", e);
            return false;
        }*/
    }

    //设置远程目录
    @Override
    public boolean setRemoteDir(String path) {
        String d = path;
        try {
            sftp.cd("/");
            sftp.cd(d);
            return true;
        } catch (Exception e) {
            logError("sftp进远程目录失败[" + d + "]，准备创建", e);
        }

        mkdir(d);
        try {
            sftp.cd(d);
            return true;
        } catch (Exception e) {
            logError("sftp设置远程目录失败[" + d + "]", e);
            return false;
        }
    }


    private boolean openRemoteDir(String path) {
        String d = path;
        try {
            sftp.cd(d);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 创建指定文件夹
     *
     * @param dirName dirName
     */
    public void mkdir(String dirName) {
        String[] dirs = dirName.split("/");
        try {
            String now = sftp.pwd();
            for (int i = 0; i < dirs.length; i++) {
                if (StringUtil.isEmpty(dirs[i])) continue;
                boolean dirExists = openRemoteDir(dirs[i]);
                if (!dirExists) {
                    sftp.mkdir(dirs[i]);
                    sftp.cd(dirs[i]);
                }
            }
            sftp.cd(now);
        } catch (SftpException e) {
            logError("sftp创建远程目录错误", e);
        }
    }

    //删除远程路径
    @Override
    public void deleteFolder(String path) {
        try {
            String d = path;
            sftp.rmdir(d);
        } catch (Exception e) {
            logError("sftp删除远程目录失败", e);
        }
    }

    /**
     * 从ftp下载文件到本地
     *
     * @param remotePath 远程路径
     * @param fileName   文件名
     * @param localPath  本地路径（不带文件名）
     */
    @Override
    public void downloadFile(String remotePath, String fileName, String localPath) throws Exception {
        downloadFileEx(remotePath, fileName, localPath + File.separator + fileName);
    }

    public boolean isFileExists(String filePath, String fileName) throws Exception {
        Vector<ChannelSftp.LsEntry> vector = sftp.ls(filePath);
        Iterator<ChannelSftp.LsEntry> sftpFileNames = vector.iterator();
        while (sftpFileNames.hasNext()) {
            String name = sftpFileNames.next().getFilename();
            if (name.equals(fileName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 下载文件到本地
     *
     * @param remotePath    远程路径
     * @param fileName      远程文件名
     * @param localFileName 本地完全文件名（路径+文件名）
     */
    @Override
    public void downloadFileEx(String remotePath, String fileName, String localFileName) throws Exception {
        if (!isFileExists(remotePath, fileName)) {
            throw new Exception();
        }
        File file = null;
        file = new File(localFileName);
        if (!file.exists()) UtilFile.CreateFile(localFileName);
        FileOutputStream output = null;

        try {
            String now = sftp.pwd();
            sftp.cd(remotePath);
            UtilLogger.info("跳转到SFTP服务目录：" + remotePath);

            output = new FileOutputStream(file);
            sftp.get(fileName, output);
            sftp.cd(now);
        } catch (Exception e) {
            file.deleteOnExit();
            throw e;
        } finally {
            IOUtils.closeQuietly(output);
        }
    }

    /**
     * 下载文件成流，使用此方法需要注意，关闭流之后要调用completePendingCommand方法
     *
     * @param remotePath 远程路径
     * @param fileName   远程文件名
     * @return 文件流
     */
    @Override
    public InputStream downloadFile(String remotePath, String fileName) throws Exception {
        String now = sftp.pwd();
        sftp.cd(remotePath);
        //setRemoteDir(remotePath);//转移到FTP服务器目录  暂时注释  拷贝错误
        InputStream is = sftp.get(fileName);
        sftp.cd(now);
        return is;

    }

    /**
     * 上传文件到ftp
     *
     * @param path          远程路径
     * @param filename      远程文件名
     * @param localFileName 本地文件（含路径）
     * @return
     */
    @Override
    public boolean uploadFile(String path, String filename, String localFileName) throws Exception {
        return uploadFile(path, filename, new File(localFileName));
    }

    @Override
    public boolean uploadFile(String path, String filename, File localFile) throws Exception {
        FileInputStream is = null;
        try {
            String now = sftp.pwd();
            setRemoteDir(path);
            is = new FileInputStream(localFile);
            try {
                sftp.put(is, filename);
            } finally {
                IOUtils.closeQuietly(is);
            }
            sftp.cd(now);
            if (!existsFile(path, filename)) {
                throw new Exception("SFTP上传失败，请检查服务器配置！");
            }
            return true;
        } finally {
            if (is != null) is.close();
        }
    }

    /**
     * 删除远程文件
     *
     * @param path     远程路径
     * @param filename 远程文件名
     */
    @Override
    public void deleteFile(String path, String filename) {
        try {
            String now = sftp.pwd();
            setRemoteDir(path);
            sftp.rm(filename);
            sftp.cd(now);
        } catch (Exception e) {
            logError("删除文件失败", e);
        }
    }

    @Override
    public boolean rename(String path, String from, String to) throws Exception {
        try {
            String now = sftp.pwd();
            setRemoteDir(path);
            sftp.rename(from, to);
            sftp.cd(now);
            return true;
        } catch (Exception e) {
            logError("重命名文件失败", e);
            return false;
        }
    }

    //调用返回流的方法，在处理完流后，要调用此方法。
    @Override
    public boolean completePendingCommand() {
        return true;
    }

    //断开连接
    @Override
    public String[] listFileNames(String remotePath) throws Exception {
        List<String> list = new ArrayList<>();
        Vector<ChannelSftp.LsEntry> sftpFile = sftp.ls(remotePath);
        ChannelSftp.LsEntry isEntity = null;
        String fileName = null;
        Iterator<ChannelSftp.LsEntry> sftpFileNames = sftpFile.iterator();
        while (sftpFileNames.hasNext()) {
            isEntity = sftpFileNames.next();
            if (!isEntity.getAttrs().isDir()) {
                fileName = isEntity.getFilename();

                list.add(fileName);
            }
        }
        return list.toArray(new String[list.size()]);
    }

    //断开连接
    @Override
    public String[] listAllFileNames(String remotePath) throws Exception {
        List<String> list = new ArrayList<>();
        Vector<ChannelSftp.LsEntry> sftpFile = sftp.ls(remotePath);
        ChannelSftp.LsEntry isEntity = null;
        String fileName = null;
        Iterator<ChannelSftp.LsEntry> sftpFileNames = sftpFile.iterator();
        while (sftpFileNames.hasNext()) {
            isEntity = sftpFileNames.next();
            fileName = isEntity.getFilename();
            list.add(fileName);
        }
        return list.toArray(new String[list.size()]);
    }


    //断开连接
    @Override
    public void disconnect() {
        if (sftp != null && isAutoFree() && sftp.isConnected()) {
            try {
                if (sftp.getSession() != null) {
                    UtilLogger.debug("关闭连接 " + sftp.getSession().getHost() + "；");
                    sftp.getSession().disconnect();
                    UtilLogger.debug("Session disconnect.");
                }
            } catch (JSchException e) {
                logError("关闭失败！", e);
            }
            sftp.quit();
            sftp.disconnect();
            UtilLogger.debug("Channel closed.");
            sftp = null;
        }
    }

    private void logError(String msg, Exception e) {
        UtilLogger.debug(msg + "：" + e.getMessage());
    }

    //编码转换
    private static String gbktoiso8859(String gbk) {
        try {
            if (gbk == null)
                return "";
            else     //iso-8859-1
                return new String(gbk.getBytes("GBK"), StandardCharsets.ISO_8859_1);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    @Override
    public boolean existsFile(String path, String filename) {
        try {
            String now = sftp.pwd();
            setRemoteDir(path);
            Vector<ChannelSftp.LsEntry> files = sftp.ls(filename);
            sftp.cd(now);
            return !CommUtil.isEmpty(files);
        } catch (SftpException e) {
            logError("判断文件是否存在失败", e);
            return false;
        }
    }
}
