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

import cc.smtweb.framework.core.util.CommUtil;
import cc.smtweb.system.bpm.util.UtilFile;
import cc.smtweb.system.bpm.util.UtilLogger;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

import java.io.*;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * 一个ftp的封装类
 */
@SuppressWarnings("UnusedDeclaration")
public class FtpUtil implements IFtpUtil {
    private static final int LOGIN_REPEAT_TIMES = 1;
    private FTPClient ftpClient;
    private boolean autoFree = true;
    private IFtpServerConfig ftpConf;

    public FtpUtil() {
        this.ftpClient = new FTPClient();
    }

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

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

    //登录ftp
    @Override
    public boolean login(IFtpServerConfig ftpConf) {
        try {
            this.ftpConf = ftpConf;
            return login(ftpConf.getServerIp(), ftpConf.getPort(), ftpConf.getUserName(), ftpConf.getPassword());
        } catch (Exception e) {
            logError("ftp登录失败", e);
            return false;
        }
    }

    //退出ftp
    @Override
    public void logout() {
        try {
            if (ftpClient.isConnected()) {
                ftpClient.logout();
            }
        } catch (IOException e) {
            logError("ftp退出登录失败", e);
        }
    }

    //是否连接正常
    @Override
    public boolean isConnected() {
        if (!ftpClient.isConnected())
            return false;
        try {
            return ftpClient.sendNoOp();
        } catch (IOException e) {
            logError("ftp测试连接失败", e);
            return false;
        }
    }

    //设置远程目录
    @Override
    public boolean setRemoteDir(String path) {
        try {
            String d = gbktoiso8859(path);
            ftpClient.changeWorkingDirectory("/");
            if (!ftpClient.changeWorkingDirectory(d)) {
                String[] paths = d.split("/");
                for (String pa : paths) {
                    if (!ftpClient.changeWorkingDirectory(pa)) {
                        ftpClient.mkd(pa);
                        ftpClient.changeWorkingDirectory(pa);
                    }
                }
            }
            return true;
        } catch (IOException e) {
            logError("ftp设置远程目录失败", e);
            return false;
        }
    }

    /**
     * 创建指定文件夹
     *
     * @param dirName dirName
     */
    public void mkdir(String dirName) {
        setRemoteDir("/");
        dirName = gbktoiso8859(dirName);

        try {
            ftpClient.makeDirectory(dirName);
        } catch (Exception e) {
            logError("ftp创建远程目录错误", e);
        }
    }

    //删除远程路径
    @Override
    public void deleteFolder(String path) {
        try {
            String d = gbktoiso8859(path);
            ftpClient.removeDirectory(d);
        } catch (IOException e) {
            logError("ftp删除远程目录失败", 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 void setPassiveNatWorkaroundStrategy() {
        ftpClient.setPassiveNatWorkaroundStrategy(new FTPClient.HostnameResolver() {
            @Override
            public String resolve(String hostname) throws UnknownHostException {
                return ftpConf.getServerIp();
            }
        });
    }

    /**
     * 下载文件到本地
     *
     * @param remotePath    远程路径
     * @param fileName      远程文件名
     * @param localFileName 本地完全文件名（路径+文件名）
     */
    @Override
    public void downloadFileEx(String remotePath, String fileName, String localFileName) throws Exception {
        setPassiveNatWorkaroundStrategy();
        ftpClient.enterLocalPassiveMode();
        setRemoteDir(remotePath);//转移到FTP服务器目录
        UtilLogger.info("跳转到FTP服务目录：" + remotePath);
        File localFile = new File(localFileName);
        if (!localFile.exists()) UtilFile.CreateFile(localFileName);
        FileOutputStream is = null;
        try {
            is = new FileOutputStream(localFile);
            ftpClient.retrieveFile(gbktoiso8859(fileName), is);
        } catch (IOException e) {
            localFile.deleteOnExit();
        } finally {
            is.close();
        }
    }

    /**
     * 下载文件成流，使用此方法需要注意，关闭流之后要调用completePendingCommand方法
     *
     * @param remotePath 远程路径
     * @param fileName   远程文件名
     * @return 文件流
     */
    @Override
    public InputStream downloadFile(String remotePath, String fileName) throws Exception {
        try {
            setPassiveNatWorkaroundStrategy();
            ftpClient.enterLocalPassiveMode();
            setRemoteDir(remotePath);//转移到FTP服务器目录
            return ftpClient.retrieveFileStream(gbktoiso8859(fileName));
        } catch (IOException e) {
            logError("ftp下载文件失败", e);
            return null;
        }
    }

    /**
     * 上传文件到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 in = null;
        try {
            setPassiveNatWorkaroundStrategy();
            ftpClient.enterLocalPassiveMode();
            in = new FileInputStream(localFile);
            setRemoteDir(path);
            boolean b = ftpClient.storeFile(gbktoiso8859(filename), in);
            if (!b) throw new Exception("FTP上传失败，请检查服务器配置！");
            return b;
        } finally {
            if (in != null) in.close();
        }
    }

    /**
     * 删除远程文件
     *
     * @param path     远程路径
     * @param filename 远程文件名
     */
    @Override
    public void deleteFile(String path, String filename) {
        try {
            setPassiveNatWorkaroundStrategy();
            setRemoteDir(path);
            ftpClient.dele(gbktoiso8859(filename));
        } catch (IOException e) {
            logError("删除文件失败", e);
        }
    }

    @Override
    public boolean existsFile(String path, String filename) {
        try {
            setPassiveNatWorkaroundStrategy();
            ftpClient.enterLocalPassiveMode();//设置被动模式
            setRemoteDir(path);
            String[] files = ftpClient.listNames(gbktoiso8859(filename));
            return !CommUtil.isEmpty(files);
        } catch (IOException e) {
            logError("判断文件是否存在失败", e);
            return false;
        }
    }

    @Override
    public boolean rename(String path, String from, String to) {
        try {
            setPassiveNatWorkaroundStrategy();
            setRemoteDir(path);
            return ftpClient.rename(gbktoiso8859(from), gbktoiso8859(to));
        } catch (IOException e) {
            logError("重命名文件失败", e);
            return false;
        }
    }

    //调用返回流的方法，在处理完流后，要调用此方法。
    @Override
    public boolean completePendingCommand() {
        try {
            setPassiveNatWorkaroundStrategy();
            return ftpClient.completePendingCommand();
        } catch (IOException e) {
            UtilLogger.error("ftp关闭流出错", e);
            return false;
        }
    }

    //断开连接
    public FTPFile[] listFiles(String remotePath) throws IOException {
        setPassiveNatWorkaroundStrategy();
        return ftpClient.listFiles(remotePath);
    }

    //断开连接
    @Override
    public String[] listFileNames(String remotePath) throws Exception {
        setPassiveNatWorkaroundStrategy();
        setRemoteDir("/");
        setRemoteDir(remotePath);
        ftpClient.enterLocalPassiveMode();//设置被动模式
        String[] files = ftpClient.listNames("");
        List<String> list = new ArrayList<>();
        for (String s : files) {
            if (!ftpClient.changeWorkingDirectory(s)) {
                s = new String(s.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
                list.add(s);
            } else {
                ftpClient.changeToParentDirectory();
            }
        }
        return list.toArray(new String[list.size()]);
    }

    //断开连接
    @Override
    public String[] listAllFileNames(String remotePath) throws Exception {
        setPassiveNatWorkaroundStrategy();
        setRemoteDir("/");
        setRemoteDir(remotePath);
        ftpClient.enterLocalPassiveMode();//设置被动模式
        String[] files = ftpClient.listNames("");
        List<String> list = new ArrayList<>();
        for (String s : files) {
            s = new String(s.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            list.add(s);
        }
        return list.toArray(new String[list.size()]);
    }


    //断开连接
    @Override
    public void disconnect() {
        if (isAutoFree() && ftpClient.isConnected()) {
            try {
                setPassiveNatWorkaroundStrategy();
                ftpClient.disconnect();
            } catch (IOException ignored) {
            }
        }
    }

    //登录
    protected boolean login(String server, int port, String user, String password) throws Exception {
        for (int i = 0; i < LOGIN_REPEAT_TIMES; i++) {
            ftpClient.setConnectTimeout(10000);
            //if (!ftpClient.isConnected()) {
            connect(server, port);
            if (!ftpClient.isConnected()) continue;
            //}
            if (ftpClient.login(user, password)) {
                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                ftpClient.setDataTimeout(120000);
                return true;
            }
            ftpClient.logout();
            Thread.sleep(100L);
        }
        disconnect();
        return false;
    }

    //连接
    private void connect(String server, int port) throws Exception {
        disconnect();
        ftpClient.setDefaultPort(port);
        ftpClient.connect(server);
        int reply = ftpClient.getReplyCode();
        if (!FTPReply.isPositiveCompletion(reply)) {
            ftpClient.disconnect();
            throw new Exception("服务器拒绝连接。");
        }
    }

    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 "";
        }
    }

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