package com.qqchen.deploy.backend.deploy.service.impl;

import com.qqchen.deploy.backend.deploy.converter.RemoteFileInfoConverter;
import com.qqchen.deploy.backend.deploy.dto.RemoteFileInfoDTO;
import com.qqchen.deploy.backend.deploy.entity.Server;
import com.qqchen.deploy.backend.deploy.service.IServerService;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.enums.SSHEvent;
import com.qqchen.deploy.backend.framework.enums.SSHTargetType;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.exception.UploadCancelledException;
import com.qqchen.deploy.backend.framework.ssh.SSHCommandServiceFactory;
import com.qqchen.deploy.backend.framework.ssh.file.AbstractSSHFileOperations;
import com.qqchen.deploy.backend.framework.ssh.file.AsyncFileUploadService;
import com.qqchen.deploy.backend.framework.ssh.file.FileUploadTask;
import com.qqchen.deploy.backend.framework.ssh.file.SSHFileInfo;
import com.qqchen.deploy.backend.framework.ssh.websocket.SSHTarget;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import net.schmizz.sshj.sftp.FileAttributes;
import net.schmizz.sshj.sftp.FileMode;
import net.schmizz.sshj.sftp.RemoteResourceInfo;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.xfer.InMemorySourceFile;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

/**
 * 服务器SSH文件操作服务（Deploy层）
 * 
 * 继承Framework的AbstractSSHFileOperations
 * 实现服务器文件管理的具体业务逻辑
 */
@Slf4j
@Service
public class ServerSSHFileService extends AbstractSSHFileOperations {
    
    @Resource
    private IServerService serverService;
    
    @Resource
    private RemoteFileInfoConverter remoteFileInfoConverter;
    
    @Resource
    private AsyncFileUploadService asyncFileUploadService;
    
    /**
     * 构造函数
     */
    public ServerSSHFileService(SSHCommandServiceFactory sshCommandServiceFactory) {
        super(sshCommandServiceFactory);
    }
    
    // ========== 公共API方法（返回DTO） ==========
    
    /**
     * 浏览远程目录（返回DTO）
     * 
     * @param serverId 服务器ID
     * @param path 目录路径
     * @return 文件DTO列表
     */
    public List<RemoteFileInfoDTO> listDirectoryAsDTO(Long serverId, String path) {
        List<SSHFileInfo> files = listDirectory(serverId, path);
        return files.stream()
            .map(remoteFileInfoConverter::toDTO)
            .collect(Collectors.toList());
    }
    
    /**
     * 上传文件（返回上传结果DTO）
     * 
     * @param serverId 服务器ID
     * @param file 要上传的文件
     * @param remotePath 远程目标路径（完整路径，包括文件名）
     * @param overwrite 是否覆盖已存在的文件
     * @return 上传结果DTO
     */
    public com.qqchen.deploy.backend.deploy.dto.FileUploadResultDTO uploadFileAsDTO(
            Long serverId, MultipartFile file, String remotePath, boolean overwrite) {
        SSHFileInfo fileInfo = uploadFile(serverId, file, remotePath, overwrite);
        return remoteFileInfoConverter.toUploadResultDTO(fileInfo);
    }
    
    /**
     * 异步上传文件（立即返回任务ID）
     * 
     * @param serverId 服务器ID
     * @param file 文件
     * @param remotePath 远程路径
     * @param overwrite 是否覆盖
     * @return 任务ID
     */
    public String uploadFileAsync(Long serverId, MultipartFile file, 
                                   String remotePath, boolean overwrite) {
        // ⚠️ 关键：在HTTP请求结束前，先复制文件内容到内存
        // 因为异步任务执行时，Spring已经清理了MultipartFile的临时文件
        final byte[] fileBytes;
        final String fileName = file.getOriginalFilename();
        final long fileSize = file.getSize();
        
        try {
            fileBytes = file.getBytes();  // 将文件内容读入内存
        } catch (IOException e) {
            throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"读取文件失败: " + e.getMessage()});
        }
        
        // 准备元数据（使用HashMap，可变）
        Map<String, Object> metadata = new HashMap<>();
        metadata.put("serverId", serverId);
        metadata.put("remotePath", remotePath);
        metadata.put("overwrite", overwrite);
        
        // 提交任务到Framework层
        return asyncFileUploadService.submitTask(
            serverId,  // 🔒 传递serverId
            fileName,
            fileSize,
            metadata,
            progressCallback -> {
                // 执行SSH文件上传（带进度）
                // 使用字节数组创建临时的MultipartFile包装
                uploadFileWithProgressFromBytes(serverId, fileBytes, fileName, 
                    remotePath, overwrite, progressCallback::onProgress);
            }
        );
    }
    
    /**
     * 查询上传任务状态
     * 
     * @param serverId 服务器ID
     * @param taskId 任务ID
     * @return 任务信息
     * @throws BusinessException 如果任务不存在或不属于该服务器
     */
    public FileUploadTask getUploadTaskStatus(Long serverId, String taskId) {
        FileUploadTask task = asyncFileUploadService.getTaskStatus(serverId, taskId);
        
        if (task == null) {
            throw new BusinessException(ResponseCode.DATA_NOT_FOUND, 
                new Object[]{"任务不存在或不属于该服务器"});
        }
        
        return task;
    }
    
    /**
     * 取消上传任务
     * 
     * @param serverId 服务器ID
     * @param taskId 任务ID
     * @return 是否取消成功
     */
    public boolean cancelUploadTask(Long serverId, String taskId) {
        return asyncFileUploadService.cancelTask(serverId, taskId);
    }
    
    /**
     * 获取上传任务统计
     */
    public Map<String, Integer> getUploadTaskStatistics() {
        return asyncFileUploadService.getTaskStatistics();
    }
    
    /**
     * 从字节数组上传文件（带进度）
     * 
     * @param serverId 服务器ID
     * @param fileBytes 文件字节数组
     * @param fileName 文件名
     * @param remotePath 远程目标路径
     * @param overwrite 是否覆盖
     * @param progressCallback 进度回调
     * @return 文件信息
     */
    private SSHFileInfo uploadFileWithProgressFromBytes(Long serverId, byte[] fileBytes, 
                                                        String fileName, String remotePath, 
                                                        boolean overwrite,
                                                        BiConsumer<Long, Long> progressCallback) {
        validatePath(remotePath);
        
        if (fileBytes == null || fileBytes.length == 0) {
            throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"文件不能为空"});
        }
        
        SSHTarget target = buildSSHTarget(serverId);
        final long fileSize = fileBytes.length;
        final AtomicLong uploadedBytes = new AtomicLong(0);
        
        return executeFileOperation(target, new FileOperation<SSHFileInfo>() {
            @Override
            public SSHFileInfo execute(SFTPClient sftpClient) throws Exception {
                // 检查文件是否已存在
                if (!overwrite) {
                    try {
                        sftpClient.stat(remotePath);
                        throw new BusinessException(ResponseCode.SSH_FILE_ALREADY_EXISTS);
                    } catch (IOException e) {
                        // 文件不存在，继续上传
                    }
                }
                
                // 使用字节数组创建输入流并监控进度
                try (InputStream inputStream = new java.io.ByteArrayInputStream(fileBytes)) {
                    ProgressMonitorInputStream progressStream = new ProgressMonitorInputStream(
                        inputStream, fileSize, uploadedBytes, progressCallback);
                    
                    try {
                        sftpClient.put(new InMemorySourceFile() {
                            @Override
                            public String getName() {
                                return fileName;
                            }
                            
                            @Override
                            public long getLength() {
                                return fileSize;
                            }
                            
                            @Override
                            public InputStream getInputStream() {
                                return progressStream;
                            }
                        }, remotePath);
                        
                        FileAttributes attrs = sftpClient.stat(remotePath);
                        return convertToSSHFileInfo(remotePath, attrs);
                        
                    } catch (UploadCancelledException e) {
                        // 🗑️ 用户取消上传，删除部分上传的文件
                        try {
                            sftpClient.rm(remotePath);
                            log.info("已删除部分上传的文件: path={}, uploadedBytes={}/{}", 
                                remotePath, uploadedBytes.get(), fileSize);
                        } catch (IOException deleteEx) {
                            log.warn("删除部分上传文件失败: path={}", remotePath, deleteEx);
                        }
                        throw e;  // 重新抛出原异常
                    }
                }
            }
            
            @Override
            public String getName() {
                return "上传文件(带进度)";
            }
        });
    }
    
    /**
     * 带进度回调的文件上传
     * 
     * @param serverId 服务器ID
     * @param file 要上传的文件
     * @param remotePath 远程目标路径（完整路径，包括文件名）
     * @param overwrite 是否覆盖已存在的文件
     * @param progressCallback 进度回调函数 (已上传字节数, 总字节数)
     * @return 文件信息
     */
    public SSHFileInfo uploadFileWithProgress(Long serverId, MultipartFile file,
                                              String remotePath, boolean overwrite,
                                              BiConsumer<Long, Long> progressCallback) {
        validatePath(remotePath);
        
        if (file == null || file.isEmpty()) {
            throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"文件不能为空"});
        }
        
        SSHTarget target = buildSSHTarget(serverId);
        final long fileSize = file.getSize();
        final AtomicLong uploadedBytes = new AtomicLong(0);
        
        return executeFileOperation(target, new FileOperation<SSHFileInfo>() {
            @Override
            public SSHFileInfo execute(SFTPClient sftpClient) throws Exception {
                // 检查文件是否已存在
                if (!overwrite) {
                    try {
                        sftpClient.stat(remotePath);
                        throw new BusinessException(ResponseCode.SSH_FILE_ALREADY_EXISTS);
                    } catch (IOException e) {
                        // 文件不存在，继续上传
                    }
                }
                
                // 包装输入流以监控进度
                try (InputStream inputStream = file.getInputStream()) {
                    ProgressMonitorInputStream progressStream = new ProgressMonitorInputStream(
                        inputStream, fileSize, uploadedBytes, progressCallback);
                    
                    sftpClient.put(new InMemorySourceFile() {
                        @Override
                        public String getName() {
                            return file.getOriginalFilename();
                        }
                        
                        @Override
                        public long getLength() {
                            return fileSize;
                        }
                        
                        @Override
                        public InputStream getInputStream() {
                            return progressStream;
                        }
                    }, remotePath);
                    
                    FileAttributes attrs = sftpClient.stat(remotePath);
                    return convertToSSHFileInfo(remotePath, attrs);
                }
            }
            
            @Override
            public String getName() {
                return "上传文件(带进度)";
            }
        });
    }
    
    // ========== 公共API方法（返回实体） ==========
    
    /**
     * 浏览远程目录
     * 
     * @param serverId 服务器ID
     * @param path 目录路径
     * @return 文件列表
     */
    public List<SSHFileInfo> listDirectory(Long serverId, String path) {
        // 验证路径
        validatePath(path);
        
        // 获取SSH目标
        SSHTarget target = buildSSHTarget(serverId);
        
        // 执行文件操作
        return executeFileOperation(target, new FileOperation<List<SSHFileInfo>>() {
            @Override
            public List<SSHFileInfo> execute(SFTPClient sftpClient) throws Exception {
                List<RemoteResourceInfo> resources = sftpClient.ls(path);
                
                return resources.stream()
                    .filter(resource -> !".".equals(resource.getName()) && !"..".equals(resource.getName()))
                    .map(resource -> convertToSSHFileInfo(resource))
                    .collect(Collectors.toList());
            }
            
            @Override
            public String getName() {
                return "浏览目录";
            }
        });
    }
    
    /**
     * 创建目录
     * 
     * @param serverId 服务器ID
     * @param path 目录路径
     */
    public void createDirectory(Long serverId, String path) {
        // 验证路径
        validatePath(path);
        
        // 获取SSH目标
        SSHTarget target = buildSSHTarget(serverId);
        
        // 执行文件操作
        executeFileOperation(target, new FileOperation<Void>() {
            @Override
            public Void execute(SFTPClient sftpClient) throws Exception {
                // 递归创建目录
                sftpClient.mkdirs(path);
                return null;
            }
            
            @Override
            public String getName() {
                return "创建目录";
            }
        });
    }
    
    /**
     * 删除文件或目录
     * 
     * @param serverId 服务器ID
     * @param path 文件/目录路径
     * @param recursive 是否递归删除（目录时使用）
     */
    public void deleteFile(Long serverId, String path, boolean recursive) {
        // 验证路径
        validatePath(path);
        
        // 获取SSH目标
        SSHTarget target = buildSSHTarget(serverId);
        
        // 执行文件操作
        executeFileOperation(target, new FileOperation<Void>() {
            @Override
            public Void execute(SFTPClient sftpClient) throws Exception {
                // 检查文件类型
                FileAttributes attrs = sftpClient.stat(path);
                
                if (attrs.getType() == FileMode.Type.DIRECTORY) {
                    if (recursive) {
                        // 递归删除目录
                        deleteDirectoryRecursive(sftpClient, path);
                    } else {
                        // 只删除空目录
                        sftpClient.rmdir(path);
                    }
                } else {
                    // 删除文件
                    sftpClient.rm(path);
                }
                
                return null;
            }
            
            @Override
            public String getName() {
                return "删除文件";
            }
        });
    }
    
    /**
     * 上传文件
     * 
     * @param serverId 服务器ID
     * @param file 要上传的文件
     * @param remotePath 远程目标路径（完整路径，包括文件名）
     * @param overwrite 是否覆盖已存在的文件
     * @return 文件信息
     */
    public SSHFileInfo uploadFile(Long serverId, MultipartFile file, String remotePath, boolean overwrite) {
        // 验证路径
        validatePath(remotePath);
        
        // 验证文件
        if (file == null || file.isEmpty()) {
            throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"文件不能为空"});
        }
        
        // 获取SSH目标
        SSHTarget target = buildSSHTarget(serverId);
        
        // 执行文件操作
        return executeFileOperation(target, new FileOperation<SSHFileInfo>() {
            @Override
            public SSHFileInfo execute(SFTPClient sftpClient) throws Exception {
                // 检查文件是否已存在
                if (!overwrite) {
                    try {
                        sftpClient.stat(remotePath);
                        throw new BusinessException(ResponseCode.ERROR, 
                            new Object[]{"文件已存在: " + remotePath});
                    } catch (IOException e) {
                        // 文件不存在，继续上传
                    }
                }
                
                // 使用内存流上传（避免临时文件）
                sftpClient.put(new InMemorySourceFile() {
                    @Override
                    public String getName() {
                        return file.getOriginalFilename();
                    }
                    
                    @Override
                    public long getLength() {
                        return file.getSize();
                    }
                    
                    @Override
                    public InputStream getInputStream() throws IOException {
                        return file.getInputStream();
                    }
                }, remotePath);
                
                // 获取上传后的文件信息
                FileAttributes attrs = sftpClient.stat(remotePath);
                return convertToSSHFileInfo(remotePath, attrs);
            }
            
            @Override
            public String getName() {
                return "上传文件";
            }
        });
    }
    
    /**
     * 重命名文件或目录
     * 
     * @param serverId 服务器ID
     * @param oldPath 原路径
     * @param newPath 新路径
     */
    public void renameFile(Long serverId, String oldPath, String newPath) {
        // 验证路径
        validatePath(oldPath);
        validatePath(newPath);
        
        // 获取SSH目标
        SSHTarget target = buildSSHTarget(serverId);
        
        // 执行文件操作
        executeFileOperation(target, new FileOperation<Void>() {
            @Override
            public Void execute(SFTPClient sftpClient) throws Exception {
                sftpClient.rename(oldPath, newPath);
                return null;
            }
            
            @Override
            public String getName() {
                return "重命名文件";
            }
        });
    }
    
    // ========== 辅助方法 ==========
    
    /**
     * 从服务器ID构建SSH目标
     */
    private SSHTarget buildSSHTarget(Long serverId) {
        Server server = serverService.findEntityById(serverId);
        if (server == null) {
            throw new BusinessException(ResponseCode.DATA_NOT_FOUND, 
                new Object[]{"服务器不存在: " + serverId});
        }
        
        SSHTarget target = new SSHTarget();
        target.setTargetType(SSHTargetType.SERVER);
        target.setHost(server.getHostIp());
        target.setPort(server.getSshPort());
        target.setUsername(server.getSshUser());
        target.setAuthType(server.getAuthType());
        target.setPassword(server.getSshPassword());
        target.setPrivateKey(server.getSshPrivateKey());
        target.setPassphrase(server.getSshPassphrase());
        target.setOsType(server.getOsType());
        target.setMetadata(serverId);
        
        return target;
    }
    
    /**
     * 转换RemoteResourceInfo为SSHFileInfo
     */
    private SSHFileInfo convertToSSHFileInfo(RemoteResourceInfo resource) {
        FileAttributes attrs = resource.getAttributes();
        String fileName = resource.getName();
        
        return SSHFileInfo.builder()
            .name(fileName)
            .path(resource.getPath())
            .isDirectory(attrs.getType() == FileMode.Type.DIRECTORY)
            .size(attrs.getSize())
            .permissionMask(attrs.getMode().getPermissionsMask())
            .permissionString(formatPermissions(attrs.getMode().getPermissionsMask()))
            .modifyTime(new Date(attrs.getMtime() * 1000L))
            .ownerUid(attrs.getUID())
            .groupGid(attrs.getGID())
            .extension(extractExtension(fileName))
            .isSymlink(attrs.getType() == FileMode.Type.SYMLINK)
            .build();
    }
    
    /**
     * 转换FileAttributes为SSHFileInfo（用于上传后获取文件信息）
     */
    private SSHFileInfo convertToSSHFileInfo(String path, FileAttributes attrs) {
        String fileName = path.substring(path.lastIndexOf('/') + 1);
        
        return SSHFileInfo.builder()
            .name(fileName)
            .path(path)
            .isDirectory(attrs.getType() == FileMode.Type.DIRECTORY)
            .size(attrs.getSize())
            .permissionMask(attrs.getMode().getPermissionsMask())
            .permissionString(formatPermissions(attrs.getMode().getPermissionsMask()))
            .modifyTime(new Date(attrs.getMtime() * 1000L))
            .ownerUid(attrs.getUID())
            .groupGid(attrs.getGID())
            .extension(extractExtension(fileName))
            .isSymlink(attrs.getType() == FileMode.Type.SYMLINK)
            .build();
    }
    
    /**
     * 格式化权限为字符串（如：rwxr-xr-x）
     */
    private String formatPermissions(int permissionMask) {
        StringBuilder sb = new StringBuilder();
        
        // 所有者权限
        sb.append((permissionMask & 0400) != 0 ? 'r' : '-');
        sb.append((permissionMask & 0200) != 0 ? 'w' : '-');
        sb.append((permissionMask & 0100) != 0 ? 'x' : '-');
        
        // 组权限
        sb.append((permissionMask & 0040) != 0 ? 'r' : '-');
        sb.append((permissionMask & 0020) != 0 ? 'w' : '-');
        sb.append((permissionMask & 0010) != 0 ? 'x' : '-');
        
        // 其他用户权限
        sb.append((permissionMask & 0004) != 0 ? 'r' : '-');
        sb.append((permissionMask & 0002) != 0 ? 'w' : '-');
        sb.append((permissionMask & 0001) != 0 ? 'x' : '-');
        
        return sb.toString();
    }
    
    /**
     * 提取文件扩展名
     */
    private String extractExtension(String fileName) {
        if (fileName == null || fileName.isEmpty()) {
            return null;
        }
        
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex > 0 && dotIndex < fileName.length() - 1) {
            return fileName.substring(dotIndex + 1).toLowerCase();
        }
        
        return null;
    }
    
    /**
     * 递归删除目录
     */
    private void deleteDirectoryRecursive(SFTPClient sftpClient, String path) throws IOException {
        List<RemoteResourceInfo> resources = sftpClient.ls(path);
        
        for (RemoteResourceInfo resource : resources) {
            String name = resource.getName();
            if (".".equals(name) || "..".equals(name)) {
                continue;
            }
            
            String itemPath = path.endsWith("/") ? path + name : path + "/" + name;
            FileAttributes attrs = resource.getAttributes();
            
            if (attrs.getType() == FileMode.Type.DIRECTORY) {
                // 递归删除子目录
                deleteDirectoryRecursive(sftpClient, itemPath);
            } else {
                // 删除文件
                sftpClient.rm(itemPath);
            }
        }
        
        // 删除空目录
        sftpClient.rmdir(path);
    }
    
    // ========== 重写钩子方法（事件处理） ==========
    
    @Override
    protected void beforeFileOperation(SSHTarget target, String operationName) {
        super.beforeFileOperation(target, operationName);
        log.info("开始文件操作: operation={}, serverId={}", operationName, target.getMetadata());
    }
    
    @Override
    protected void afterFileOperation(SSHTarget target, String operationName, Object result) {
        super.afterFileOperation(target, operationName, result);
        log.info("文件操作成功: operation={}, serverId={}", operationName, target.getMetadata());
        
        // TODO: 可以在这里触发事件，记录审计日志
        // 例如：eventPublisher.publishEvent(new SSHFileEvent(...))
    }
    
    @Override
    protected void onFileOperationError(SSHTarget target, String operationName, Exception error) {
        super.onFileOperationError(target, operationName, error);
        log.error("文件操作失败: operation={}, serverId={}, error={}", 
            operationName, target.getMetadata(), error.getMessage());
        
        // TODO: 可以在这里触发错误事件
    }
    
    /**
     * 进度监控输入流
     * 
     * 包装原始输入流，在读取时记录进度并回调
     */
    private static class ProgressMonitorInputStream extends FilterInputStream {
        private final long totalSize;
        private final AtomicLong uploadedBytes;
        private final BiConsumer<Long, Long> progressCallback;
        private long lastReportedBytes = 0;
        private static final long REPORT_INTERVAL = 1024 * 1024; // 每1MB报告一次
        
        public ProgressMonitorInputStream(InputStream in, long totalSize,
                                         AtomicLong uploadedBytes,
                                         BiConsumer<Long, Long> progressCallback) {
            super(in);
            this.totalSize = totalSize;
            this.uploadedBytes = uploadedBytes;
            this.progressCallback = progressCallback;
        }
        
        @Override
        public int read() throws IOException {
            int b = super.read();
            if (b != -1) {
                updateProgress(1);
            }
            return b;
        }
        
        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            int bytesRead = super.read(b, off, len);
            if (bytesRead > 0) {
                updateProgress(bytesRead);
            }
            return bytesRead;
        }
        
        private void updateProgress(int bytes) {
            long current = uploadedBytes.addAndGet(bytes);
            
            // 每1MB或结束时报告一次进度，避免回调过于频繁
            if (current - lastReportedBytes >= REPORT_INTERVAL || current >= totalSize) {
                lastReportedBytes = current;
                if (progressCallback != null) {
                    progressCallback.accept(current, totalSize);
                }
            }
        }
    }
}
