package cd.casic.ci.process.util;

import cd.casic.framework.commons.exception.ServiceException;
import com.jcraft.jsch.*;
import org.springframework.web.multipart.MultipartFile;

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

public class SftpUploadUtil {

    private static final int DEFAULT_SFTP_PORT = 22;

    /**
     * 通过 SFTP 上传文件到远程服务器
     *
     * @param remoteHost       远程服务器IP或主机名
     * @param remotePort       远程服务器端口 (通常是 22)，为 null 或 <= 0 时使用默认端口 22
     * @param username         远程服务器用户名
     * @param password         远程服务器密码 (如果使用密码认证)
     * @param sshKeyPath       SSH Key 文件路径 (如果使用密钥认证，password 参数可以为 null)
     * @param localFilePath    本地要上传的文件路径
     * @param remoteDir        远程服务器存放文件的目录 (例如: /home/user/uploads/)
     * @param remoteFileName   上传到远程服务器的文件名 (为 null 或空字符串时使用本地文件名)
     * @throws SftpUploadException 如果上传过程中发生任何错误
     */
    public static void uploadFileViaSftp(
            String remoteHost,
            Integer remotePort,
            String username,
            String password,
            String sshKeyPath,
            String localFilePath,
            String remoteDir,
            String remoteFileName) throws SftpUploadException {

        Session session = null;
        Channel channel = null;
        ChannelSftp channelSftp = null;
        FileInputStream fis = null;

        try {
            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 SftpUploadException("SSH Key 文件不存在或不是一个有效文件: " + sshKeyPath);
                }
                jsch.addIdentity(sshKeyPath);
                System.out.println("使用 SSH Key 认证: " + sshKeyPath);
            } else if (password == null || password.trim().isEmpty()) {
                // 如果没有提供密码或密钥路径，则认证信息不全
                throw new 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 = session.openChannel("sftp");
            channel.connect();
            System.out.println("SFTP Channel 打开成功.");

            channelSftp = (ChannelSftp) channel;

            // 5. 检查并切换到远程目录
            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 SftpUploadException("远程目录创建失败: " + remoteDir, e2);
                }
            }

            // 6. 获取本地文件流
            File localFile = new File(localFilePath);
            if (!localFile.exists() || !localFile.isFile()) {
                throw new 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("文件上传成功!");

        } catch (JSchException e) {
            throw new SftpUploadException("SFTP 连接或认证失败: " + e.getMessage(), e);
        } catch (SftpException e) {
            throw new SftpUploadException("SFTP 操作失败 (如切换目录或上传文件): " + e.getMessage(), e);
        } catch (FileNotFoundException e) {
            throw new SftpUploadException("本地文件未找到: " + e.getMessage(), e);
        } catch (IOException e) {
            throw new SftpUploadException("文件流读写异常: " + e.getMessage(), e);
        } catch (SftpUploadException e) {
            // 重新抛出自定义异常
            throw e;
        } catch (Exception e) {
            // 捕获其他未知异常
            throw new SftpUploadException("SFTP 上传过程中发生未知异常: " + e.getMessage(), e);
        } finally {
            // 9. 关闭资源 (确保在任何情况下都关闭)
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    System.err.println("关闭本地文件流失败: " + e.getMessage());
                    e.printStackTrace(); // 打印堆栈以便调试
                }
            }
            if (channelSftp != null) {
                channelSftp.disconnect();
                System.out.println("SFTP Channel 已断开.");
            }
            if (channel != null) {
                channel.disconnect();
                System.out.println("SFTP Channel 资源已释放.");
            }
            if (session != null) {
                session.disconnect();
                System.out.println("SFTP Session 已断开.");
            }
        }
    }



    /**
     * 通过 SFTP 上传文件到远程服务器
     *
     * @param remoteHost     远程服务器IP或主机名
     * @param remotePort     远程服务器端口 (通常是 22)，为 null 或 <= 0 时使用默认端口 22
     * @param username       远程服务器用户名
     * @param password       远程服务器密码 (如果使用密码认证)
     * @param sshKeyPath     SSH Key 文件路径 (如果使用密钥认证，password 参数可以为 null)
     * @param file           本地要上传的文件路径
     * @param remoteDir      远程服务器存放文件的目录 (例如: /home/user/uploads/)
     * @param remoteFileName 上传到远程服务器的文件名 (为 null 或空字符串时使用本地文件名)
     * @return
     * @throws SftpUploadException 如果上传过程中发生任何错误
     */
    public static String uploadFileSftp(
            String remoteHost,
            Integer remotePort,
            String username,
            String password,
            String sshKeyPath,
            MultipartFile file,
            String remoteDir,
            String remoteFileName) throws SftpUploadException {

        Session session = null;
        Channel channel = null;
        ChannelSftp channelSftp = null;
        InputStream fis = null;

        try {
            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 SftpUploadException("SSH Key 文件不存在或不是一个有效文件: " + sshKeyPath);
                }
                jsch.addIdentity(sshKeyPath);
                System.out.println("使用 SSH Key 认证: " + sshKeyPath);
            } else if (password == null || password.trim().isEmpty()) {
                // 如果没有提供密码或密钥路径，则认证信息不全
                throw new 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 = session.openChannel("sftp");
            channel.connect();
            System.out.println("SFTP Channel 打开成功.");

            channelSftp = (ChannelSftp) channel;

            // 5. 检查并切换到远程目录
            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 SftpUploadException("远程目录创建失败: " + remoteDir, e2);
                }
            }

            // 6. 获取本地文件流
            fis = file.getInputStream();
            System.out.println("本地文件流获取成功: " + file);

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

            // 8. 上传文件
            channelSftp.put(fis, finalRemoteFileName);
            System.out.println("文件上传成功!");
            return remoteDir + "/" + finalRemoteFileName;
        } catch (JSchException e) {
            throw new SftpUploadException("SFTP 连接或认证失败: " + e.getMessage(), e);
        } catch (SftpException e) {
            throw new SftpUploadException("SFTP 操作失败 (如切换目录或上传文件): " + e.getMessage(), e);
        } catch (FileNotFoundException e) {
            throw new SftpUploadException("本地文件未找到: " + e.getMessage(), e);
        } catch (IOException e) {
            throw new SftpUploadException("文件流读写异常: " + e.getMessage(), e);
        } catch (SftpUploadException e) {
            // 重新抛出自定义异常
            throw e;
        } catch (Exception e) {
            // 捕获其他未知异常
            throw new SftpUploadException("SFTP 上传过程中发生未知异常: " + e.getMessage(), e);
        } finally {
            // 9. 关闭资源 (确保在任何情况下都关闭)
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    System.err.println("关闭本地文件流失败: " + e.getMessage());
                    e.printStackTrace(); // 打印堆栈以便调试
                }
            }
            if (channelSftp != null) {
                channelSftp.disconnect();
                System.out.println("SFTP Channel 已断开.");
            }
            if (channel != null) {
                channel.disconnect();
                System.out.println("SFTP Channel 资源已释放.");
            }
            if (session != null) {
                session.disconnect();
                System.out.println("SFTP Session 已断开.");
            }
        }
    }



    /**
     * 通过 SFTP 上传文件到远程服务器
     *
     * @param remoteHost       远程服务器IP或主机名
     * @param remotePort       远程服务器端口 (通常是 22)，为 null 或 <= 0 时使用默认端口 22
     * @param username         远程服务器用户名
     * @param password         远程服务器密码 (如果使用密码认证)
     * @param sshKeyPath       SSH Key 文件路径 (如果使用密钥认证，password 参数可以为 null)
     * @param remoteFilePath    要下载的文件地址（包含文件名）
     * @throws SftpUploadException 如果上传过程中发生任何错误
     */
    public static InputStream downloadFileSftp(
            String remoteHost,
            Integer remotePort,
            String username,
            String password,
            String sshKeyPath,
            String remoteFilePath) throws SftpUploadException {

        Session session = null;
        Channel channel = null;
        ChannelSftp channelSftp = null;
        InputStream inputStream = null;

        try {
            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 SftpUploadException("SSH Key 文件不存在或不是一个有效文件: " + sshKeyPath);
                }
                jsch.addIdentity(sshKeyPath);
                System.out.println("使用 SSH Key 认证: " + sshKeyPath);
            } else if (password == null || password.trim().isEmpty()) {
                // 如果没有提供密码或密钥路径，则认证信息不全
                throw new 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 = session.openChannel("sftp");
            channel.connect();
            System.out.println("SFTP Channel 打开成功.");

            channelSftp = (ChannelSftp) channel;

            // 获取远程文件名和目录
            String remoteDir = remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/'));
            String fileName = remoteFilePath.substring(remoteFilePath.lastIndexOf('/') + 1);

            // 切换目录并列出内容用于调试
            try {
                channelSftp.cd(remoteDir);
            } catch (SftpException e) {
                throw new 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);
            } catch (SftpException e) {
                throw new SftpUploadException("获取远程文件流失败: " + fileName, e);
            }

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

            return inputStream;
        } catch (JSchException e) {
            throw new SftpUploadException("SFTP 连接或认证失败: " + e.getMessage(), e);
        } catch (SftpException e) {
            throw new SftpUploadException("SFTP 操作失败 (如切换目录或上传文件): " + e.getMessage(), e);
        } catch (SftpUploadException e) {
            // 重新抛出自定义异常
            throw e;
        } catch (Exception e) {
            // 捕获其他未知异常
            throw new SftpUploadException("SFTP 上传过程中发生未知异常: " + e.getMessage(), e);
        } finally {
            // 9. 关闭资源 (确保在任何情况下都关闭)
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    System.err.println("关闭本地文件流失败: " + e.getMessage());
                    e.printStackTrace(); // 打印堆栈以便调试
                }
            }
            if (channelSftp != null) {
                channelSftp.disconnect();
                System.out.println("SFTP Channel 已断开.");
            }
            if (channel != null) {
                channel.disconnect();
                System.out.println("SFTP Channel 资源已释放.");
            }
            if (session != null) {
                session.disconnect();
                System.out.println("SFTP Session 已断开.");
            }
        }
    }



    /**
     * 辅助方法：递归创建远程目录
     * @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());
                // System.out.println("目录已存在: " + 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);
        }
    }

    // 示例用法
    public static void main(String[] args) {
        // 使用密码认证示例
        try {
            System.out.println("--- 开始通过密码认证 SFTP 上传 ---");
            SftpUploadUtil.uploadFileViaSftp(
                    "your-remote-server-ip", // 替换为你的远程服务器IP或主机名
                    22,                         // SFTP端口 (如果不是22，请修改)
                    "your-username",            // 替换为你的 SFTP 用户名
                    "your-password",            // 替换为你的 SFTP 密码
                    null,                       // SSH Key 路径 (密码认证时为 null)
                    "path/to/your/local/file.txt", // 替换为你要上传的本地文件路径
                    "/upload/target/directory/", // 替换为远程服务器上的目标目录 (确保用户有写入权限)
                    "uploaded_file.txt"         // 上传到远程服务器的文件名 (null 或空则使用本地文件名)
            );
            System.out.println("文件通过密码认证 SFTP 上传成功!");
        } catch (SftpUploadException e) {
            System.err.println("文件通过密码认证 SFTP 上传失败: " + e.getMessage());
            e.printStackTrace();
        }

        System.out.println("\n------------------------------------\n");

        // 使用 SSH Key 认证示例 (假设你有一个私钥文件)
        // 请确保你的 SSH Key 不需要密码 (passphrase)
        try {
            System.out.println("--- 开始通过 SSH Key 认证 SFTP 上传 ---");
            SftpUploadUtil.uploadFileViaSftp(
                    "your-remote-server-ip", // 替换为你的远程服务器IP或主机名
                    null,                       // SFTP端口 (使用默认端口 22)
                    "your-username",            // 替换为你的 SFTP 用户名
                    null,                       // 密码 (SSH Key 认证时为 null)
                    "/path/to/your/private/key",// 替换为你的本地私钥文件路径 (例如: /home/user/.ssh/id_rsa)
                    "path/to/another/local/file.log", // 替换为另一个你要上传的本地文件路径
                    "/upload/other/files/",     // 替换为另一个远程目录
                    null                       // 远程文件名使用本地文件名
            );
            System.out.println("文件通过 SSH Key 认证 SFTP 上传成功!");
        } catch (SftpUploadException e) {
            System.err.println("文件通过 SSH Key 认证 SFTP 上传失败: " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println("\n------------------------------------\n");
    }
}
