package com.njcn.relational.utils;

/**
 * @Author: cdf
 * @CreateTime: 2026-05-26
 * @Description: SFTP 文件传输工具类（支持上传和下载）
 */

import com.jcraft.jsch.*;
import com.njcn.relational.pojo.bo.DownloadResult;
import com.njcn.relational.pojo.bo.UploadResult;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * SFTP 文件传输工具类
 * 功能：将当前服务器上的文件传输到另一台服务器的指定目录，或从远程服务器下载文件
 */
@Slf4j
public class SftpUploadUtil {

    // 默认端口
    private static final int DEFAULT_PORT = 22;

    /**
     * 上传文件到远程服务器
     *
     * @param host          远程服务器IP
     * @param port          远程服务器端口（默认22）
     * @param username      服务器用户名
     * @param password      服务器密码
     * @param localFilePath 本地/当前服务器文件路径（如 /data/test.txt）
     * @param remoteDir     远程目标目录（如 /opt/upload/）
     * @throws Exception 上传异常
     */
    public static void uploadFile(String host, int port, String username, String password,
                                  String localFilePath, String remoteDir) throws Exception {

        JSch jsch = new JSch();
        Session session = null;
        ChannelSftp channelSftp = null;
        InputStream inputStream = null;

        try {
            // 1. 创建SSH会话
            session = jsch.getSession(username, host, port);
            session.setPassword(password);

            // 2. 设置配置（跳过主机密钥检查，生产环境可根据安全要求调整）
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            config.put("server_host_key", "ssh-rsa,ssh-dss");
            session.setConfig(config);

            // 3. 连接服务器
            log.info("正在连接远程服务器：" + host);
            session.connect();
            log.info("服务器连接成功！");

            // 4. 打开SFTP通道
            Channel channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp) channel;

            // 5. 如果远程目录不存在，自动创建（支持多级目录）
            try {
                channelSftp.cd(remoteDir);
            } catch (SftpException e) {
                // 目录不存在则创建
                String[] dirs = remoteDir.split("/");
                String currentPath = "";
                for (String dir : dirs) {
                    if (dir == null || dir.isEmpty()) continue;
                    currentPath += "/" + dir;
                    try {
                        channelSftp.cd(currentPath);
                    } catch (SftpException e1) {
                        channelSftp.mkdir(currentPath);
                        channelSftp.cd(currentPath);
                    }
                }
                log.info("远程目录已创建：" + remoteDir);
            }

            // 6. 读取本地文件
            File localFile = new File(localFilePath);
            if (!localFile.exists()) {
                throw new FileNotFoundException("本地文件不存在: " + localFilePath);
            }
            inputStream = new FileInputStream(localFile);

            // 7. 上传文件
            log.info("开始上传文件：" + localFile.getName());
            channelSftp.put(inputStream, localFile.getName());
            log.info("文件上传完成！远程路径：" + remoteDir + localFile.getName());

        } finally {
            // 8. 关闭流和连接
            if (inputStream != null) inputStream.close();
            if (channelSftp != null && channelSftp.isConnected()) channelSftp.disconnect();
            if (session != null && session.isConnected()) session.disconnect();
            log.info("连接已关闭");
        }
    }


    /**
     * 批量上传文件到远程服务器（共享同一个Session连接）
     *
     * @param host           远程服务器IP
     * @param port           远程服务器端口（默认22）
     * @param username       服务器用户名
     * @param password       服务器密码
     * @param localFilePaths 本地文件路径列表
     * @param remoteDir      远程目标目录（如 /opt/upload/）
     * @return 上传结果列表，包含每个文件的上传状态
     * @throws Exception 连接异常
     */
    /**
     * 批量上传文件到远程服务器（共享同一个Session连接）【稳定版：支持保活 + 重试】
     */
    public static List<UploadResult> batchUploadFiles(String host, int port, String username, String password,
                                                      List<String> localFilePaths, String remoteDir) throws Exception {

        List<UploadResult> results = new ArrayList<>();
        JSch jsch = new JSch();
        Session session = null;
        ChannelSftp channelSftp = null;

        try {
            session = jsch.getSession(username, host, port);
            session.setPassword(password);

            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            config.put("server_host_key", "ssh-rsa,ssh-dss");

            session.setConfig(config);

            session.setTimeout(30000);
            // ✅ 保活间隔：每 10 秒发一次心跳
            session.setServerAliveInterval(10000);
            session.setServerAliveCountMax(5);

            log.info("正在连接远程服务器：" + host + ":" + port);
            session.connect();
            log.info("服务器连接成功！");

            Channel channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp) channel;
            channelSftp.setFilenameEncoding("UTF-8");


            log.info("开始批量上传文件，共 " + localFilePaths.size() + " 个文件, 上传目录: " + remoteDir);

            for (String localFilePath : localFilePaths) {
                UploadResult result = new UploadResult();
                result.setLocalFilePath(localFilePath);

                // ✅ 失败自动重试 3 次
                int retry = 3;
                boolean uploadOk = false;

                while (retry > 0 && !uploadOk) {
                    try (InputStream inputStream = new FileInputStream(localFilePath)) {
                        File localFile = new File(localFilePath);
                        if (!localFile.exists()) {
                            result.setSuccess(false);
                            result.setErrorMessage("文件不存在");
                            break;
                        }

                        String fileName = localFile.getName();
                        // ✅ 拼接完整绝对路径
                        String remoteFullPath = remoteDir + (remoteDir.endsWith("/") ? "" : "/") + fileName;

                        // ✅ 直接用完整路径上传，不依赖 cd 目录
                        channelSftp.put(inputStream, remoteFullPath);
                        // ✅ 用完整路径校验，绝对不会错
                        channelSftp.stat(remoteFullPath);

                        uploadOk = true;

                        result.setSuccess(true);
                        result.setRemotePath(remoteFullPath);
                        result.setFileSize(localFile.length());
                        log.info("上传成功: " + fileName + " 剩余重试：" + retry);

                    } catch (Exception e) {
                        log.info(e.getMessage());
                        retry--;
                        log.error("上传失败，剩余重试次数 " + retry + "：" + localFilePath);
                        if (retry == 0) {
                            result.setSuccess(false);
                            result.setErrorMessage("最终失败：" + e.getMessage());
                        }
                        Thread.sleep(1000);
                    }
                }

                results.add(result);
            }

            long successCount = results.stream().filter(UploadResult::isSuccess).count();
            long failCount = results.size() - successCount;
            log.info("批量上传完成！成功: " + successCount + ", 失败: " + failCount);

        } finally {
            safeDisconnect(session,channelSftp);
            log.info("连接已关闭");
        }

        return results;
    }

    /**
     * 安全的关闭方法
     */
    private static void safeDisconnect(Session session, ChannelSftp channelSftp) {
        if (channelSftp != null) {
            try {
                if (channelSftp.isConnected()) {
                    channelSftp.exit();  // 退出 SFTP 通道
                    channelSftp.disconnect();
                }
            } catch (Exception e) {
                log.error("关闭 SFTP 通道失败: " + e.getMessage());
            }
        }

        if (session != null) {
            try {
                if (session.isConnected()) {
                    session.disconnect();
                }
            } catch (Exception e) {
                log.error("关闭 Session 失败: " + e.getMessage());
            }
        }
    }
    /**
     * 从远程服务器下载文件
     *
     * @param host          远程服务器IP
     * @param port          远程服务器端口（默认22）
     * @param username      服务器用户名
     * @param password      服务器密码
     * @param remoteFilePath 远程文件路径（如 /opt/data/test.txt）
     * @param localDir      本地目标目录（如 /data/download/）
     * @return 下载后的本地文件路径
     * @throws Exception 下载异常
     */
    public static String downloadFile(String host, int port, String username, String password,
                                      String remoteFilePath, String localDir,
                                      boolean deleteAfterDownload) throws Exception {

        JSch jsch = new JSch();
        Session session = null;
        ChannelSftp channelSftp = null;
        OutputStream outputStream = null;

        try {
            // 1. 创建SSH会话
            session = jsch.getSession(username, host, port);
            session.setPassword(password);

            // 2. 设置配置
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            config.put("server_host_key", "ssh-rsa,ssh-dss");
            session.setConfig(config);

            // 3. 连接服务器
            log.info("正在连接远程服务器：" + host);
            session.connect();
            log.info("服务器连接成功！");

            // 4. 打开SFTP通道
            Channel channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp) channel;

            // 5. 检查远程文件是否存在
            try {
                channelSftp.stat(remoteFilePath);
            } catch (SftpException e) {
                throw new FileNotFoundException("远程文件不存在: " + remoteFilePath);
            }

            // 6. 创建本地目录（如果不存在）
            File localDirFile = new File(localDir);
            if (!localDirFile.exists()) {
                localDirFile.mkdirs();
                log.info("创建本地目录：" + localDir);
            }

            // 7. 生成本地文件路径
            String fileName = remoteFilePath.substring(remoteFilePath.lastIndexOf("/") + 1);
            String localFilePath = localDir + fileName;
            outputStream = new FileOutputStream(new File(localFilePath));

            // 8. 下载文件
            log.info("开始下载文件：" + fileName);
            channelSftp.get(remoteFilePath, outputStream);
            log.info("文件下载完成！本地路径：" + localFilePath);


            // 下载成功后删除远程文件
            if (deleteAfterDownload) {
                try {
                    channelSftp.rm(remoteFilePath);
                    log.info("已删除远程文件: " + remoteFilePath);
                } catch (SftpException e) {
                    log.error("删除远程文件失败: " + remoteFilePath + ", " + e.getMessage());
                }
            }
            return localFilePath;

        } finally {
            // 9. 关闭流和连接
            if (outputStream != null) outputStream.close();
            if (channelSftp != null && channelSftp.isConnected()) channelSftp.disconnect();
            if (session != null && session.isConnected()) session.disconnect();
            log.info("连接已关闭");
        }
    }



    /**
     * 批量下载并删除远程文件（下载成功后删除远程文件）
     *
     * @param host              远程服务器IP
     * @param port              远程服务器端口
     * @param username          用户名
     * @param password          密码
     * @param remoteFilePaths   远程文件路径列表
     * @param localDir          本地目标目录
     * @param deleteAfterDownload 下载后是否删除远程文件
     * @return 下载结果列表
     * @throws Exception 连接异常
     */
    public static List<DownloadResult> batchDownloadAndDelete(String host, int port, String username, String password,
                                                              List<String> remoteFilePaths, String localDir,
                                                              boolean deleteAfterDownload) throws Exception {

        List<DownloadResult> results = new ArrayList<>();
        JSch jsch = new JSch();
        Session session = null;
        ChannelSftp channelSftp = null;

        try {
            // 创建并连接
            session = jsch.getSession(username, host, port);
            session.setPassword(password);
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            config.put("server_host_key", "ssh-rsa,ssh-dss");
            session.setConfig(config);
            session.setTimeout(30000);
            session.connect();
            log.info("服务器连接成功！");

            Channel channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp) channel;

            // 创建本地目录
            File localDirFile = new File(localDir);
            if (!localDirFile.exists()) {
                localDirFile.mkdirs();
                log.info("创建本地目录：" + localDir);
            }

            log.info("开始批量下载文件，共 " + remoteFilePaths.size() + " 个文件");

            for (String remoteFilePath : remoteFilePaths) {
                DownloadResult result = new DownloadResult();
                result.setRemoteFilePath(remoteFilePath);

                OutputStream outputStream = null;
                try {
                    String fileName = remoteFilePath.substring(remoteFilePath.lastIndexOf("/") + 1);
                    String localFilePath = localDir + (localDir.endsWith("/") ? "" : "/") + fileName;

                    outputStream = new FileOutputStream(new File(localFilePath));
                    channelSftp.get(remoteFilePath, outputStream);

                    result.setSuccess(true);
                    result.setLocalFilePath(localFilePath);
                    log.info("下载成功: " + fileName);

                    // 下载成功后删除远程文件
                    if (deleteAfterDownload) {
                        try {
                            channelSftp.rm(remoteFilePath);
                            log.info("已删除远程文件: " + remoteFilePath);
                        } catch (SftpException e) {
                            log.error("删除远程文件失败: " + remoteFilePath + ", " + e.getMessage());
                        }
                    }

                } catch (Exception e) {
                    result.setSuccess(false);
                    result.setErrorMessage(e.getMessage());
                    log.error("下载失败: " + remoteFilePath + ", 错误: " + e.getMessage());
                } finally {
                    if (outputStream != null) {
                        try {
                            outputStream.close();
                        } catch (IOException e) {
                            // 忽略
                        }
                    }
                }
                results.add(result);
            }

            long successCount = results.stream().filter(DownloadResult::isSuccess).count();
            long failCount = results.size() - successCount;
            log.info("批量下载完成！成功: " + successCount + ", 失败: " + failCount);

        } finally {
            if (channelSftp != null && channelSftp.isConnected()) {
                channelSftp.disconnect();
            }
            if (session != null && session.isConnected()) {
                session.disconnect();
            }
            log.info("连接已关闭");
        }

        return results;
    }


    /**
     * 列出远程目录下的文件
     *
     * @param host      远程服务器IP
     * @param port      远程服务器端口
     * @param username  用户名
     * @param password  密码
     * @param remoteDir 远程目录
     * @return 文件列表
     * @throws Exception 异常
     */
    public static java.util.Vector<ChannelSftp.LsEntry> listFiles(String host, int port,
                                                                  String username, String password,
                                                                  String remoteDir) throws Exception {
        JSch jsch = new JSch();
        Session session = null;
        ChannelSftp channelSftp = null;

        try {
            session = jsch.getSession(username, host, port);
            session.setPassword(password);
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            config.put("server_host_key", "ssh-rsa,ssh-dss");

            session.setConfig(config);
            session.connect();

            Channel channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp) channel;

            return channelSftp.ls(remoteDir);

        }catch (Exception e){
            log.error("查看远程目录失败{}",remoteDir,e);
            throw new RuntimeException(e);
        }finally {
            if (channelSftp != null && channelSftp.isConnected()) channelSftp.disconnect();
            if (session != null && session.isConnected()) session.disconnect();
        }
    }

    /**
     * 删除远程文件
     *
     * @param host      远程服务器IP
     * @param port      远程服务器端口
     * @param username  用户名
     * @param password  密码
     * @param remoteFilePath 远程文件路径
     * @throws Exception 异常
     */
    public static void deleteRemoteFile(String host, int port, String username, String password,
                                        String remoteFilePath) throws Exception {
        JSch jsch = new JSch();
        Session session = null;
        ChannelSftp channelSftp = null;

        try {
            session = jsch.getSession(username, host, port);
            session.setPassword(password);
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            config.put("server_host_key", "ssh-rsa,ssh-dss");

            session.setConfig(config);
            session.connect();

            Channel channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp) channel;

            channelSftp.rm(remoteFilePath);
            log.info("远程文件已删除：" + remoteFilePath);

        } finally {
            if (channelSftp != null && channelSftp.isConnected()) channelSftp.disconnect();
            if (session != null && session.isConnected()) session.disconnect();
        }
    }

    /**
     * 上传文件（使用默认端口22）
     */
    public static void uploadFile(String host, String username, String password,
                                  String localFilePath, String remoteDir) throws Exception {
        uploadFile(host, DEFAULT_PORT, username, password, localFilePath, remoteDir);
    }


}
