package cd.casic.ci.commons.sftp;

import cd.casic.framework.commons.exception.ServiceException;
import com.amazonaws.util.IOUtils;
import com.jcraft.jsch.*;

import java.io.*;
import java.util.List;
import java.util.Vector;

/**
 * @author HopeLi
 * @version v1.0
 * @ClassName SftpClientUtils
 * @Date: 2025/7/22 15:09
 * @Description:
 */
public class SftpClientUtils implements AutoCloseable {
    private static final int DEFAULT_SFTP_PORT = 22;

    private Session session;
    private ChannelSftp channelSftp;

    public SftpClientUtils(String remoteHost, Integer remotePort, String username,
                      String password, String sshKeyPath) throws JSchException, SftpException, SftpClientUtils.SftpUploadException {
        JSch jsch = new JSch();

        // 1. 添加身份认证信息 (密码或密钥)
        if (sshKeyPath != null && !sshKeyPath.trim().isEmpty()) {
            // 使用 SSH Key 认证
            File sshKeyFile = new File(sshKeyPath);
            if (!sshKeyFile.exists() || !sshKeyFile.isFile()) {
                throw new SftpClientUtils.SftpUploadException("SSH Key 文件不存在或不是一个有效文件: " + sshKeyPath);
            }
            jsch.addIdentity(sshKeyPath);
            System.out.println("使用 SSH Key 认证: " + sshKeyPath);
        } else if (password == null || password.trim().isEmpty()) {
            // 如果没有提供密码或密钥路径，则认证信息不全
            throw new SftpClientUtils.SftpUploadException("必须提供密码或 SSH Key 路径进行 SFTP 认证.");
        }
        // 如果提供了密码，将在 getSession 后设置，因为 getSession 需要用户名、主机和端口先建立连接意图


        // 2. 获取 Session
        int port = (remotePort != null && remotePort > 0) ? remotePort : DEFAULT_SFTP_PORT;
        session = jsch.getSession(username, remoteHost, port);
        System.out.println("尝试连接 SFTP 服务器: " + username + "@" + remoteHost + ":" + port);


        // 如果使用密码认证且提供了密码
        if (password != null && !password.trim().isEmpty() && (sshKeyPath == null || sshKeyPath.trim().isEmpty())) {
            session.setPassword(password);
            System.out.println("使用密码认证.");
        }

        // 设置连接不进行主机密钥检查 (生产环境不推荐，应该配置 known_hosts)
        // 在实际应用中，应该引导用户信任主机密钥或提前将主机密钥加入 known_hosts
        java.util.Properties config = new java.util.Properties();
        config.put("StrictHostKeyChecking", "no"); // !!! 生产环境请谨慎使用或配置正确的主机密钥检查 !!!
        session.setConfig(config);

        // 3. 连接 Session
        session.connect();
        System.out.println("SFTP Session 连接成功.");

        // 4. 打开 SFTP Channel
        Channel channel = session.openChannel("sftp");
        channel.connect();
        System.out.println("SFTP Channel 打开成功.");

        channelSftp = (ChannelSftp) channel;
    }

    public String uploadFile(String localFilePath, String remoteDir, String remoteFileName) throws SftpException, SftpClientUtils.SftpUploadException, FileNotFoundException {
        FileInputStream fis = null;

        // 检查并切换到远程目录
        try {
            channelSftp.cd(remoteDir);
            System.out.println("已切换到远程目录: " + remoteDir);
        } catch (SftpException e) {
            // 如果远程目录不存在，尝试创建
            System.err.println("远程目录不存在: " + remoteDir + "，尝试创建...");
            try {
                // 尝试递归创建目录 (如果需要)
                createRemoteDirRecursive(channelSftp, remoteDir);
                channelSftp.cd(remoteDir); // 创建后再次切换
                System.out.println("远程目录创建成功并已切换: " + remoteDir);
            } catch (SftpException e2) {
                // 创建目录失败
                throw new SftpClientUtils.SftpUploadException("远程目录创建失败: " + remoteDir, e2);
            }
        }

        // 6. 获取本地文件流
        File localFile = new File(localFilePath);
        if (!localFile.exists() || !localFile.isFile()) {
            throw new SftpClientUtils.SftpUploadException("本地文件不存在或不是一个有效文件: " + localFilePath);
        }
        fis = new FileInputStream(localFile);
        System.out.println("本地文件流获取成功: " + localFilePath);

        // 7. 确定远程文件名
        String finalRemoteFileName = (remoteFileName != null && !remoteFileName.trim().isEmpty()) ?
                remoteFileName : localFile.getName();
        System.out.println("最终上传到远程的文件名为: " + finalRemoteFileName);

        // 8. 上传文件
        channelSftp.put(fis, finalRemoteFileName);
        System.out.println("文件上传成功!");

        return remoteDir +  "/" + remoteFileName;
    }

    public void downloadFile(String remoteFilePath, OutputStream outputStream) throws SftpException, SftpClientUtils.SftpUploadException, IOException {
        InputStream inputStream = null;

        String remoteDir = remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/'));
        String fileName = remoteFilePath.substring(remoteFilePath.lastIndexOf('/') + 1);

        // 切换目录并列出内容用于调试
        try {
            channelSftp.cd(remoteDir);
        } catch (SftpException e) {
            throw new SftpClientUtils.SftpUploadException("切换远程目录失败: " + remoteDir, e);
        }

        // 列出目录内容用于调试
        Vector<ChannelSftp.LsEntry> entries = channelSftp.ls(".");
        List<String> fileNames = entries.stream()
                .map(entry -> entry.getFilename())
                .filter(name -> !name.equals(".") && !name.equals(".."))
                .toList();

        System.out.println("远程目录中的文件列表: " + fileNames);

        // 尝试获取文件流
        try {
            inputStream = channelSftp.get(fileName);

            if (inputStream == null) {
                throw new SftpClientUtils.SftpUploadException("无法获取远程文件输入流，请检查文件是否存在或被其他进程占用: " + fileName);
            }

            IOUtils.copy(inputStream,outputStream);
            outputStream.flush();
        } catch (SftpException e) {
            throw new SftpClientUtils.SftpUploadException("文件下载失败: " + remoteFilePath, e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    System.err.println("关闭 InputStream 时发生异常: " + e.getMessage());
                }
            }
        }
    }

    public InputStream downloadFileToStream(String remoteFilePath) throws SftpException, SftpClientUtils.SftpUploadException {
        InputStream inputStream = null;

        String remoteDir = remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/'));
        String fileName = remoteFilePath.substring(remoteFilePath.lastIndexOf('/') + 1);

        // 切换目录并列出内容用于调试
        try {
            channelSftp.cd(remoteDir);
        } catch (SftpException e) {
            throw new SftpClientUtils.SftpUploadException("切换远程目录失败: " + remoteDir, e);
        }

        // 列出目录内容用于调试
        Vector<ChannelSftp.LsEntry> entries = channelSftp.ls(".");
        List<String> fileNames = entries.stream()
                .map(entry -> entry.getFilename())
                .filter(name -> !name.equals(".") && !name.equals(".."))
                .toList();

        System.out.println("远程目录中的文件列表: " + fileNames);

        // 尝试获取文件流
        try {
            inputStream = channelSftp.get(fileName);

            if (inputStream == null) {
                throw new SftpClientUtils.SftpUploadException("无法获取远程文件输入流，请检查文件是否存在或被其他进程占用: " + fileName);
            }

            return inputStream;
        } catch (SftpException e) {
            throw new SftpClientUtils.SftpUploadException("获取远程文件流失败: " + fileName, e);
        }


    }


    /**
     * 辅助方法：递归创建远程目录
     * @param channelSftp ChannelSftp 实例
     * @param remoteDir 要创建的目录路径
     * @throws SftpException 如果创建失败
     */
    private static void createRemoteDirRecursive(ChannelSftp channelSftp, String remoteDir) throws SftpException {
        // 标准化路径，去掉末尾的 /
        String cleanRemoteDir = remoteDir.endsWith("/") ? remoteDir.substring(0, remoteDir.length() - 1) : remoteDir;

        String[] pathElements = cleanRemoteDir.split("/");
        StringBuilder currentDir = new StringBuilder();
        try {
            channelSftp.cd("/"); // 先回到根目录
        } catch (SftpException e) {
            // 理论上不应该失败，除非根目录都不可访问
            throw new ServiceException();
        }

        for (String dir : pathElements) {
            if (dir == null || dir.isEmpty()) {
                continue; // 跳过空的路径元素，比如路径以/开头
            }
            currentDir.append("/").append(dir);
            try {
                channelSftp.cd(currentDir.toString());
            } catch (SftpException e) {
                if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                    // 目录不存在，创建它
                    try {
                        System.out.println("创建目录: " + currentDir.toString());
                        channelSftp.mkdir(currentDir.toString());
                        channelSftp.cd(currentDir.toString()); // 创建后进入该目录
                        System.out.println("目录创建成功并进入: " + currentDir.toString());
                    } catch (SftpException e2) {
                        throw new SftpException(e2.id, "无法创建远程目录: " + currentDir.toString(), e2);
                    }
                } else {
                    // 其他 SFTP 异常
                    throw new SftpException(e.id, "切换或检查远程目录失败: " + currentDir.toString(), e);
                }
            }
        }
    }

    // 自定义异常类，用于封装上传过程中的错误
    public static class SftpUploadException extends Exception {
        public SftpUploadException(String message) {
            super(message);
        }
        public SftpUploadException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    @Override
    public void close() {
        if (channelSftp != null) {
            channelSftp.disconnect();
        }
        if (session != null) {
            session.disconnect();
        }
    }
}
