package cn.gov.customs.wxjy.ibmmq.service.impl;

import cn.gov.customs.wxjy.ibmmq.config.MQProperties;
import cn.gov.customs.wxjy.ibmmq.enums.EnvType;
import cn.gov.customs.wxjy.ibmmq.enums.MessageType;
import cn.gov.customs.wxjy.ibmmq.model.FileMessage;
import cn.gov.customs.wxjy.ibmmq.service.IFileHandlerService;
import cn.gov.customs.wxjy.ibmmq.utils.CommonUtils;
import cn.gov.customs.wxjy.ibmmq.utils.XmlBuildTool;
import com.alibaba.fastjson2.JSONObject;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

@Service
@AllArgsConstructor
@Slf4j
public class FileHandlerServiceImpl implements IFileHandlerService {

    private JmsTemplate jmsTemplate;

    private MQProperties mqProperties;

    private XmlBuildTool xmlBuildTool;

    // 记录每个文件的发送状态：<fileId, 已发送分片索引集合>
    private final Map<String, Set<Integer>> sentShardsMap = new ConcurrentHashMap<>();

    // 记录每个文件已接收的分片：<fileId, 已接收分片索引集合>
    private final Map<String, Set<Integer>> receivedShards = new ConcurrentHashMap<>();

    /**
     * 发送文件到MQ
     *
     * @param filePath 文件路径
     * @param savePath 文件保存路径
     * @return 文件ID
     * @throws IOException              文件流异常
     * @throws NoSuchAlgorithmException 找不到哈希算法异常
     */
    @Override
    public String sendFile(String filePath, String savePath) throws IOException, NoSuchAlgorithmException {
        File file = new File(filePath);
        if (!file.exists() || !file.isFile()) {
            throw new FileNotFoundException("文件不存在: " + filePath);
        }
        if (file.length() == 0) {
            throw new FileNotFoundException("文件为空: " + filePath);
        }

        String fileId = UUID.randomUUID().toString();
        sentShardsMap.put(fileId, ConcurrentHashMap.newKeySet());
        int shardSize = calculateShardSize(file.length());

        FileMessage fileMessage = new FileMessage();

        // 1. 计算文件哈希
        String fileHash = CommonUtils.calculateFileHash(file);

        // 2. 设置文件的元数据
        fileMessage.setFileId(fileId);
        fileMessage.setFileName(file.getName());
        fileMessage.setFileSize(file.length());
        fileMessage.setFileHash(fileHash);
        fileMessage.setShardSize(shardSize);
        fileMessage.setTotalShards((int) Math.ceil((double) file.length() / shardSize));
        fileMessage.setSavePath(savePath);

        // 3. 分片并发送文件
        splitAndSendFile(file, fileMessage);

        // 4. 发送完成消息
        sendCompleteMessage(fileMessage);

        return fileId;
    }

    /**
     * 发送多个文件
     *
     * @param filePaths 文件路径
     * @return 文件ID
     * @throws IOException              文件流异常
     * @throws NoSuchAlgorithmException 找不到哈希算法异常
     */
    @Override
    public String sendFiles(List<String> filePaths) throws IOException, NoSuchAlgorithmException {
        List<String> fileIds = new ArrayList<>();
        for (String filePath : filePaths) {
            File file = new File(filePath);
            // 3. 获取父目录路径（不含文件名）
            String parentPath = file.getParent();
            String fileId = sendFile(filePath, parentPath);
            fileIds.add(fileId);
        }
        return String.join(",", fileIds);
    }

    /**
     * 处理接收到的文件消息
     *
     * @param fileMessage 文件消息
     */
    @Override
    public void processReceivedFileMessage(FileMessage fileMessage) {
        receivedShards.computeIfAbsent(fileMessage.getFileId(), k -> ConcurrentHashMap.newKeySet());
        MessageType type = fileMessage.getType();
        switch (type) {
            case SHARD:
                handleShardMessage(fileMessage);
                break;
            case COMPLETE:
                handleCompleteMessage(fileMessage);
                break;
            default:
                log.error("未知的消息类型: {}, 消息内容：{}", type, fileMessage);
        }
    }

    /**
     * 根据文件大小计算分片大小
     *
     * @param fileSize 文件大小
     * @return 分片大小
     */
    private int calculateShardSize(long fileSize) {
        // 根据文件大小动态调整分片大小，但不超过默认值
        int shardSize = mqProperties.getDefaultShardSize();
        if (fileSize < mqProperties.getDefaultShardSize()) {
            shardSize = (int) fileSize;
        }
        return shardSize;
    }

    /**
     * 分割并发送文件
     *
     * @param file        文件
     * @param fileMessage 消息对象
     * @throws IOException 文件流异常
     */
    private void splitAndSendFile(File file, FileMessage fileMessage) throws IOException {
        // 分片大小
        int shardSize = fileMessage.getShardSize();
        // 总分片数
        int totalShards = fileMessage.getTotalShards();

        try (FileInputStream fis = new FileInputStream(file)) {
            byte[] buffer = new byte[shardSize];
            int bytesRead;
            int shardIndex = 0;

            while ((bytesRead = fis.read(buffer)) != -1) {
                byte[] actualData = (bytesRead == shardSize) ? buffer : Arrays.copyOf(buffer, bytesRead);
                sendFileShard(fileMessage, shardIndex, totalShards, actualData);
                shardIndex++;
            }
        }
    }

    /**
     * 发送文件分片
     *
     * @param fileMessage 消息对象
     * @param shardIndex  分片索引
     * @param totalShards 总分片数
     * @param data        分片数据
     */
    private void sendFileShard(FileMessage fileMessage, int shardIndex, int totalShards, byte[] data) {
        try {
            // 计算分片哈希
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            digest.update(data);
            String shardHash = CommonUtils.bytesToHex(digest.digest());

            // Base64编码分片数据
            String encodedData = Base64.getEncoder().encodeToString(data);

            // 组装消息
            fileMessage.setShardIndex(shardIndex);
            fileMessage.setShardData(encodedData);
            fileMessage.setShardHash(shardHash);
            fileMessage.setType(MessageType.SHARD);

            // 获取发送消息队列
            String destinationQueue = getDestinationQueue();

            // 发送分片消息
            String xmlMsg = xmlBuildTool.generateXmlMsg(JSONObject.toJSONString(fileMessage));
            jmsTemplate.convertAndSend(destinationQueue, xmlMsg);
            sentShardsMap.get(fileMessage.getFileId()).add(shardIndex);

            log.info("发送分片: {} [{}/{}], 大小: {} bytes", fileMessage.getFileId(), shardIndex + 1, totalShards, data.length);
        } catch (Exception e) {
            log.error("发送分片失败: {} [{}/{}], 错误: {}", fileMessage.getFileId(), shardIndex + 1, totalShards, e.getMessage());
        }
    }

    /**
     * 发送完成消息
     *
     * @param fileMessage 文件信息消息
     */
    private void sendCompleteMessage(FileMessage fileMessage) {
        try {
            String fileId = fileMessage.getFileId();
            int totalShards = fileMessage.getTotalShards();
            fileMessage.setType(MessageType.COMPLETE);
            String destinationQueue = getDestinationQueue();
            // 发送完成消息
            String xmlMsg = xmlBuildTool.generateXmlMsg(JSONObject.toJSONString(fileMessage));
            jmsTemplate.convertAndSend(destinationQueue, xmlMsg);
            log.info("分片发送完成: {}, 总分片数: {}, 消息对象：{}", fileId, totalShards, xmlMsg);
        } catch (Exception e) {
            log.error("发送完成消息失败: {}, 错误: {}", fileMessage.getFileId(), e.getMessage());
        }
    }

    /**
     * 处理接收到的分片消息
     *
     * @param fileMessage 文件分片消息
     */
    private void handleShardMessage(FileMessage fileMessage) {
        String fileId = fileMessage.getFileId();
        int shardIndex = fileMessage.getShardIndex();
        int totalShards = fileMessage.getTotalShards();
        log.info("收到分片: {}, 分片序号: {}", fileId, shardIndex);

        // 创建临时目录
        Path tempDirPath = Paths.get(mqProperties.getTempDir(), fileId);
        try {
            Files.createDirectories(tempDirPath);
        } catch (IOException e) {
            log.error("创建临时目录失败: {}, 错误：{}", tempDirPath, e.getMessage());
            return;
        }

        // 解码分片数据
        byte[] data = Base64.getDecoder().decode(fileMessage.getShardData());

        // 验证分片哈希
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            digest.update(data);
            String calculatedHash = CommonUtils.bytesToHex(digest.digest());

            if (!calculatedHash.equals(fileMessage.getShardHash())) {
                log.error("分片哈希验证失败: {} [{}/{}]", fileId, shardIndex + 1, totalShards);
                return;
            }
        } catch (NoSuchAlgorithmException e) {
            log.error("哈希算法不可用: {}", e.getMessage());
            return;
        }

        // 保存分片到临时文件
        Path tempFilePath = Paths.get(mqProperties.getTempDir(), fileId, "shard_" + shardIndex);
        try {
            Files.write(tempFilePath, data);
            receivedShards.get(fileId).add(shardIndex);

            log.info("收到分片: {} [{}/{}], 大小: {} bytes", fileId, shardIndex + 1, totalShards, data.length);
        } catch (Exception e) {
            log.error("保存分片失败: {} [{}/{}], 错误原因: ", fileId, shardIndex + 1, totalShards, e);
        }
    }

    /**
     * 处理完成消息
     *
     * @param fileMessage 文件分片消息
     */
    private void handleCompleteMessage(FileMessage fileMessage) {
        String fileId = fileMessage.getFileId();
        int totalShards = fileMessage.getTotalShards();
        log.info("收到完成消息: {}", fileId);

        // 检查是否所有分片都已接收
        Set<Integer> shardSet = receivedShards.get(fileId);
        if (shardSet.size() == totalShards) {
            // 验证分片索引是否连续
            boolean allReceived = true;
            for (int i = 0; i < totalShards; i++) {
                if (!shardSet.contains(i)) {
                    allReceived = false;
                    log.error("文件分片数据不完整，缺失[{}]号分片，无法合并分片", i);
                }
            }
            if (allReceived) {
                // 所有分片都已接收，开始合并文件
                mergeFile(fileMessage);
            }
        } else {
            for (int i = 0; i < totalShards; i++) {
                if (!shardSet.contains(i)) {
                    log.error("文件分片数据不完整，缺失[{}]号分片，无法合并分片", i);
                }
            }
        }

    }

    /**
     * 组装文件
     *
     * @param fileMessage 文件消息
     */
    private void mergeFile(FileMessage fileMessage) {
        String fileId = fileMessage.getFileId();
        String fileName = fileMessage.getFileName();
        int totalShards = fileMessage.getTotalShards();
        String savePath = fileMessage.getSavePath();
        String fileHash = fileMessage.getFileHash();
        try {
            Path outputPath = Paths.get(savePath, fileName);
            // 创建输出目录
            Files.createDirectories(outputPath.getParent());

            // 创建输出文件
            try (OutputStream fos = Files.newOutputStream(outputPath, StandardOpenOption.CREATE)) {
                // 按顺序合并分片
                for (int i = 0; i < totalShards; i++) {
                    Path shardPath = Paths.get(mqProperties.getTempDir(), fileId, "shard_" + i);
                    byte[] shardData = Files.readAllBytes(shardPath);
                    fos.write(shardData);
                }
            }

            // 验证文件哈希
            String calculatedHash = CommonUtils.calculateFileHash(outputPath.toFile());
            if (calculatedHash.equals(fileHash)) {
                log.info("文件合并成功: {}, 大小:{} bytes, 哈希验证通过", fileName, Files.size(outputPath));
            } else {
                log.info("文件合并失败: {}, 哈希验证不通过", fileName);
            }

            // 清理临时文件
            cleanupTempFiles(fileId);
        } catch (Exception e) {
            log.error("文件合并异常: {}", e.getMessage());
        }
    }

    /**
     * 清理临时文件
     *
     * @param fileId 文件ID
     */
    private void cleanupTempFiles(String fileId) {
        try {
            Path tempDirPath = Paths.get(mqProperties.getTempDir(), fileId);
            // Files.walk(tempDirPath).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
            // 使用 try-with-resources 确保流关闭
            try (Stream<Path> walk = Files.walk(tempDirPath)) {
                walk.sorted(Comparator.reverseOrder())
                        .forEach(path -> {
                            try {
                                Files.delete(path); // 使用 Files.delete() 更安全，并抛出异常
                            } catch (IOException e) {
                                log.warn("删除临时文件失败: {}", path, e);
                            }
                        });
            }
            receivedShards.remove(fileId);
            log.info("清理临时文件: {}", tempDirPath);
        } catch (Exception e) {
            log.error("清理临时文件失败: {}", e.getMessage());
        }
    }

    /**
     * 获取消息目标队列名称
     * 根据环境类型确定目标队列。企业端发送到企业端文件队列，管理端发送到管理端文件队列
     *
     * @return 目标队列名称
     */
    private String getDestinationQueue() {
        if (EnvType.ENTERPRISE.name().equalsIgnoreCase(mqProperties.getEnvType())) {
            return mqProperties.getSendQueue();
        } else {
            return mqProperties.getReceiveQueue();
        }
    }
}
