package com.bcxin.signature.util.ftp;

import com.bcxin.backend.core.exceptions.SaasRetryableException;
import com.bcxin.backend.core.utils.RetryUtil;
import com.bcxin.signature.config.FileModeConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.util.CollectionUtils;

import java.io.*;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * <b> ftp客户端 </b>
 * @author ZXF
 * @create 2023/07/07 0007 13:51
 * @version
 * @注意事项 </b>
 */
@Slf4j
public class FtpConnection {
    private static FtpClientPool ftpClientPool = new FtpClientPool(
            5,
            FileModeConfig.getHost(), FileModeConfig.getPort(), FileModeConfig.getUserName(), FileModeConfig.getPassword()
    );

    private FTPClient getFtp() {
        //return _threadFtpClient.get();
        return ftpClientPool.borrowConnection();
    }

    private void returnFtp(FTPClient client) {
        if(client!=null) {
            ftpClientPool.returnConnection(client);
        }
    }


    /**
     * 构造函数
     */
    public FtpConnection() {

    }

    protected static void reconnect(FTPClient ftpClient){
        try {
            initConnect(ftpClient, FileModeConfig.getHost(), FileModeConfig.getPort(), FileModeConfig.getUserName(), FileModeConfig.getPassword());
        } catch (IOException e) {
            log.error("ftp connect exception:host={};username={}", FileModeConfig.getHost(), FileModeConfig.getUserName(), e);
            throw new RuntimeException("failed to connect ftp", e);
        }
    }

    /**
     * 初始化连接
     *
     * @param host
     * @param port
     * @param user
     * @param password
     * @throws IOException
     */
    private static void initConnect(FTPClient ftpClient, String host, int port, String user, String password) throws IOException {
        try {
            if(!ftpClient.isConnected()){
                ftpClient.connect(host, port);
            }
        } catch (UnknownHostException ex) {
            throw new IOException("Can't find FTP server '" + host + "'");
        }
        //被动模式
        ftpClient.enterLocalPassiveMode();
        int reply = ftpClient.getReplyCode();//220 连接成功
        if (!FTPReply.isPositiveCompletion(reply)) {
            disconnect();
            throw new IOException("Can't connect to server '" + host + "'");
        }
        if (!ftpClient.login(user, password)) {
            //is_connected = false;
            disconnect();
            throw new IOException("Can't login to server '" + host + "'");
        } else {
            //is_connected = true;
        }
    }

    public boolean uploadBase64(String path, String ftpFileName, String base64Content) throws IOException {
        boolean is = false;
        // 检查Base64内容是否为空
        if (base64Content == null || base64Content.isEmpty()) {
            throw new IOException("Can't upload. The base64 content is empty.");
        }
        // 转换路径和文件名，防止设置工作路径出错
        conv(path,ftpFileName);
        if(hasChineseUsingRegex(ftpFileName)){
            return is;
        }
        if(StringUtils.isNotEmpty(path)){
            path = new String(path.getBytes("GBK"), "iso-8859-1");
            // 设置工作路径
            setWorkingDirectory(path);
        }
        ftpFileName = new String(ftpFileName.getBytes("GBK"), "iso-8859-1");


        // 将Base64字符串解码为字节数组
//        byte[] decodedBytes = Base64.getDecoder().decode(base64Content);
        byte[] decodedBytes = base64Content.getBytes();

        FTPClient ftpClient = null;
        try {
            ftpClient = getFtp();
            // 上传
            try (InputStream in = new ByteArrayInputStream(decodedBytes)) {
                String filePath = path + "/" + ftpFileName;
                filePath = filePath.replace("\\", "/");
                ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE);
                // 保存文件
                is = ftpClient.storeFile(filePath, in);
            } catch (Exception e) {
                log.error("failed to uploadBase64 file", e);
            }
        }
        finally {
            returnFtp(ftpClient);
        }

        return is;
    }

    private static void conv(String path,String ftpFileName){
        ftpFileName = ftpFileName.startsWith("/")?ftpFileName:"/"+ftpFileName;
        Path sourcePath = Paths.get(path+ftpFileName);
        if(sourcePath.getParent() !=null){
            path = sourcePath.getParent().toAbsolutePath().toString();
        }
        ftpFileName = sourcePath.getFileName().toString();
    }

    public static boolean hasChineseUsingRegex(String str) {
        return str.matches(".*[\u4e00-\u9fa5]+.*");
    }

    /**
     * 下载文件
     *
     * @param path ftp操作目录
     * @param ftpFileName ftp文件名称
     * @param localFile 本地文件地址
     * @throws IOException
     */
    public boolean download(String path, String ftpFileName, File localFile) throws IOException {
        boolean is = false;
        // 检查本地文件是否存在
        if (localFile.exists()) {
            throw new IOException("Can't download '" + localFile.getAbsolutePath() + "'. This file already exists.");
        }

        // 转换路径和文件名，防止设置工作路径出错
        conv(path,ftpFileName);

        if(hasChineseUsingRegex(ftpFileName)){
            return is;
        }
        System.err.println("=======>获取ftp目录下指定文件转存,ftp路径："+path+",文件名："+ftpFileName+"<=======");
        if(StringUtils.isNotEmpty(path)){
            path = new String(path.getBytes("GBK"), "iso-8859-1");
            System.err.println("=======>获取ftp目录下指定文件转存,节点：101<=======");
            // 设置工作路径
            setWorkingDirectory(path);
        }
        ftpFileName = new String(ftpFileName.getBytes("GBK"), "iso-8859-1");
        System.err.println("=======>获取ftp目录下指定文件转存,节点：102<=======");

        FTPClient ftpClient = null;
        try {

            ftpClient = getFtp();
            // 下载
            try (OutputStream out = new BufferedOutputStream(new FileOutputStream(localFile))) {
                String filePath = path + "/" + ftpFileName;
                filePath = filePath.replace("\\", "/");
                System.err.println("=======>获取ftp目录下指定文件,实际路径：" + filePath + "<=======");
                ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE);
                // 保存文件
                is = ftpClient.retrieveFile(filePath, out);
            } catch (Exception e) {
                System.err.println("=======>获取ftp目录下指定文件转存,节点：103<=======");
                e.printStackTrace();
                log.error("");
            }
            System.err.println("=======>获取ftp目录下指定文件转存,节点：104<=======");
            return is;
        }
        finally {
            returnFtp(ftpClient);
        }
    }

    /**
     * 下载文件并返回Base64编码的字符串
     *
     * @param path ftp操作目录
     * @param ftpFileName ftp文件名称
     * @return Base64编码的字符串
     * @throws IOException
     */
    public String downloadAsBase64(String path, String ftpFileName) throws IOException {

        // 转换路径和文件名，防止设置工作路径出错
        conv(path,ftpFileName);

        if(hasChineseUsingRegex(ftpFileName)){
            return "";
        }
        if(StringUtils.isNotEmpty(path)){
            path = new String(path.getBytes("GBK"), "iso-8859-1");
            // 设置工作路径
            setWorkingDirectory(path);
        }
        ftpFileName = new String(ftpFileName.getBytes("GBK"), "iso-8859-1");
        FTPClient ftpClient = null;

        try {
            ftpClient = getFtp();
            // 下载
            try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream out = new BufferedOutputStream(baos)) {
                String filePath = path + "/" + ftpFileName;
                filePath = filePath.replace("\\", "/");
                ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE);
                // 保存文件
                boolean is = ftpClient.retrieveFile(filePath, out);
                if (!is) {
                    throw new IOException("Failed to retrieve file: " + filePath);
                }

                // 将字节数组转换为Base64编码的字符串
                byte[] fileBytes = baos.toByteArray();
                return Base64.getEncoder().encodeToString(fileBytes);
            } catch (Exception e) {
                throw new IOException("Error while downloading file: " + e.getMessage());
            }
        }
        finally {
            returnFtp(ftpClient);
        }

    }

    private static Object _staticInstanceLock = new Object();
    /**
     * 获取ftp文件内容字符串
     *
     * @param path ftp操作目录
     * @param ftpFileName ftp文件名称
     * @throws IOException
     */
    public String getFileContent(String path, String ftpFileName) throws IOException {
        // 转换路径和文件名，防止设置工作路径出错
        conv(path, ftpFileName);
        if (StringUtils.isNotEmpty(path)) {
            path = new String(path.getBytes("GBK"), "iso-8859-1");
            // 设置工作路径
            setWorkingDirectory(path);
        }
        ftpFileName = new String(ftpFileName.getBytes("GBK"), "iso-8859-1");
        // 读取文件内容
        StringBuilder content = new StringBuilder();
        ftpFileName = ftpFileName.startsWith("/") ? ftpFileName : "/" + ftpFileName;

        if (hasChineseUsingRegex(ftpFileName)) {
            log.error("[BAD-ERROR]--the file does not match expected:{}", ftpFileName);
            return "";
        }

        StringBuilder sb = new StringBuilder();
        String remoteFile = path + ftpFileName;
        sb.append(String.format("org-remote-file=%s;", remoteFile));
        if (StringUtils.isBlank(path) && ftpFileName.startsWith("/")) {
            ftpFileName = ftpFileName.substring(1);

            remoteFile = path + ftpFileName;
            sb.append(String.format("re-org-remote-file=%s;", remoteFile));
        }

        InputStream inputStream = null;
        try {
            String finalRemoteFile = remoteFile;
            AtomicInteger retryCount = new AtomicInteger(0);
            inputStream = RetryUtil.execute(() -> {
                InputStream is = null;
                synchronized (_staticInstanceLock) {
                    FTPClient selectedFtpClient = null;
                    try {
                        selectedFtpClient = getFtp();
                        is = selectedFtpClient.retrieveFileStream(finalRemoteFile);
                    } finally {
                        returnFtp(selectedFtpClient);
                    }
                }

                if (is == null) {
                    retryCount.incrementAndGet();
                    Collection<String> matchedFiles = this.getMatchFileNames(finalRemoteFile, 10);
                    if (!CollectionUtils.isEmpty(matchedFiles)) {
                        throw new SaasRetryableException(String.format("can not read remoteFile=%s;", finalRemoteFile));
                    }
                }

                if (retryCount.get() > 0) {
                    log.error("After retry success to get content with retryCount={};file={}", retryCount, finalRemoteFile);
                }

                return is;
            });

            if (inputStream == null) {
                sb.append("EMPTY;");
                return ""; // 文件未找到
            }
            try (InputStream in = new BufferedInputStream(inputStream)) {
                byte[] buffer = new byte[1024];
                int length;
                while ((length = in.read(buffer)) != -1) {
                    content.append(new String(buffer, 0, length, "UTF-8"));
                }
                sb.append("OK");

                log.warn("Success to read content:{}", sb);
            } catch (Exception e) {
                log.error("failed to read content(remoteFile={}, track={})", remoteFile, sb, e);
                throw new RuntimeException(String.format("getFileContent exception:path=%s;fileName=%s;track=%s", path, ftpFileName, sb), e);
            }
            return content.toString();
        } finally {
            if (inputStream != null) {
                IOUtils.closeQuietly(inputStream);
            }
        }
    }

    /**
     * 获取ftp目录下所有最近5分钟内生成的文件的文件名，返回文件名列表
     *
     * @param path ftp操作目录
     * @return 最近5分钟内生成的文件名列表
     * @throws IOException
     */
    public List<String> fileNames(String path,String keywork) throws IOException {
        if(StringUtils.isEmpty(keywork)){
            keywork = "IN-";
        }

        FTPClient ftpClient = null;
        try {
            ftpClient = getFtp();
            System.err.println("=======>获取ftp目录下所有文件业务,ftp路径：" + path + ",关键字：" + keywork + "<=======");
            if (StringUtils.isNotEmpty(path)) {
                System.err.println("=======>获取ftp目录下所有文件业务节点：001.1<=======");
                path = new String(path.getBytes("GBK"), "iso-8859-1");
                // 设置工作路径
                setWorkingDirectory(path);
                System.err.println("=======>获取ftp目录下所有文件业务节点：001<=======");
            }
            System.err.println("=======>获取ftp目录下所有文件业务节点：002<=======");

            List<String> recentFiles = new ArrayList<>();
            FTPFile[] files = ftpClient.listFiles();
            System.err.println("=======>获取ftp目录下所有文件业务节点：003<=======");

            long currentTime = System.currentTimeMillis();
            long fiveMinutesAgo = currentTime - (5 * 60 * 1000); // 5 minutes ago in milliseconds

            System.err.println("=======>获取ftp目录下所有文件的数量：" + files.length + "<=======");
            for (FTPFile file : files) {
                System.err.println("=======>获取ftp目录下所有文件的名称.filename：" + file.getName() + "<=======");
                //转utc 时间加8小时
                Long fileTime = file.getTimestamp().getTimeInMillis() + file.getTimestamp().getTimeZone().getOffset(0);
                if (file.isFile() && fileTime >= fiveMinutesAgo && file.getName().startsWith(keywork)) {
                    System.err.println("=======>获取ftp目录下所有文件业务有效文件名：" + file.getName() + "<=======");
                    recentFiles.add(file.getName());
                }
            }
            System.err.println("=======>获取ftp目录下所有最近5分钟内生成的文件的数量：" + recentFiles.size() + "<=======");
            return recentFiles;
        }
        finally {
            returnFtp(ftpClient);
        }


    }

    public boolean delectFile(String path,String filename) throws IOException {
        if (StringUtils.isNotEmpty(path)) {
            path = new String(path.getBytes("GBK"), "iso-8859-1");
            // 设置工作路径
            setWorkingDirectory(path);
        }

        FTPClient ftpClient = null;
        try {
            ftpClient = getFtp();
            return ftpClient.deleteFile(filename);
        } finally {
            returnFtp(ftpClient);
        }
    }

    /**
     * 关闭连接
     *
     * @throws IOException
     */
    public static void disconnect() throws IOException {
    }

    /**
     * 设置工作路径
     *
     * @param dir
     * @return
     */
    private boolean setWorkingDirectory(String dir) {
        //如果目录不存在创建目录
        try {
            if (createDirecroty(dir)) {
                FTPClient ftpClient = null;
                try {
                    ftpClient = getFtp();
                    return ftpClient.changeWorkingDirectory(dir);
                }
                finally {
                    returnFtp(ftpClient);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;

    }

    /**
     * 是否连接
     *
     * @return
     */
    public boolean isConnected() {
        ///return is_connected;

        return true;
    }

    /**
     * 创建目录
     *
     * @param remote
     * @return
     * @throws IOException
     */
    private boolean createDirecroty(String remote) throws IOException {
        if (StringUtils.isEmpty(remote)) {
            return false;
        }
        boolean success = true;
        String directory = remote.substring(0, remote.lastIndexOf("/") + 1);
        FTPClient ftpClient = null;

        try {
            ftpClient = getFtp();
            // 如果远程目录不存在，则递归创建远程服务器目录
            if (!directory.equalsIgnoreCase("/") && !ftpClient.changeWorkingDirectory(new String(directory))) {
                int start = 0;
                int end = 0;
                if (directory.startsWith("/")) {
                    start = 1;
                } else {
                    start = 0;
                }
                end = directory.indexOf("/", start);

                while (true) {
                    String subDirectory = new String(remote.substring(start, end));
                    if (!ftpClient.changeWorkingDirectory(subDirectory)) {
                        if (ftpClient.makeDirectory(subDirectory)) {
                            ftpClient.changeWorkingDirectory(subDirectory);
                        } else {
                            log.error("mack directory error :/" + subDirectory);
                            return false;
                        }
                    }
                    start = end + 1;
                    end = directory.indexOf("/", start);
                    // 检查所有目录是否创建完毕
                    if (end <= start) {
                        break;
                    }
                }

            }
        } finally {
            returnFtp(ftpClient);
        }

        return success;
    }

    public Collection<String> getMetaFileNames(String prefix,int limit) {
        Collection<String> fileNames = new ArrayList<>();
        Exception lastException = null;
        StringBuilder trace = new StringBuilder();

        if(limit<1) {
            limit = 20;
        }

        boolean successInGetPendingFiles = false;
        try {
            fileNames = getMatchFileNames(prefix,limit);
        } catch (Exception ex) {
            lastException = ex;

            successInGetPendingFiles = false;
        } finally {
            log.error("[IMPORTANT-{}] getMetaFileNames(fileNames.length={},successInGetPendingFiles={}): trace={}",
                    (lastException == null ? "SUCCESS" : "ERROR"), fileNames.size(), successInGetPendingFiles, trace, lastException);
        }

        return fileNames;
    }

    private Collection<String> getMatchFileNames(String prefix,int limit) throws IOException {
        Collection<String> fileNames =
                RetryUtil.execute(() -> {
                    String[] fnames = null;
                    FTPClient ftpClient = getFtp();
                    try {
                        fnames = ftpClient.listNames();
                    } catch (Exception ex) {
                        throw new SaasRetryableException("failed to fetch listNames", ex);
                    } finally {
                        returnFtp(ftpClient);
                    }

                    Collection<String> matchedNames = new ArrayList<>();
                    if (fnames != null) {
                        matchedNames =
                                Arrays.stream(fnames)
                                        .filter(ii -> ii.startsWith(prefix))
                                        .sorted(Comparator.reverseOrder())
                                        .limit(limit)
                                        .collect(Collectors.toList());
                        String noMatched = "";
                        if (CollectionUtils.isEmpty(matchedNames)) {
                            matchedNames =
                                    Arrays.stream(fnames)
                                            .filter(ii -> ii.toLowerCase().contains(prefix.toLowerCase()))
                                            .sorted(Comparator.reverseOrder())
                                            .limit(limit)
                                            .collect(Collectors.toList());
                            noMatched = String.format("retry.matched=%s", matchedNames.size());
                        }else {
                            noMatched = String.format("matched=%s", matchedNames.size());
                        }

                        log.error("find file(prefix={}) listNames-{} = size={};matchedNames.1={};filenames={}",
                                prefix,
                                noMatched,
                                fnames.length,
                                matchedNames.stream().findFirst().orElse(null),
                                Arrays.stream(fnames).limit(2).collect(Collectors.joining(","))
                        );
                    }

                    return matchedNames;
                });


        return fileNames;
    }
}
