package cn.iocoder.yudao.module.infra.service.file;

import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.MultipartUploadSessionDO;
import cn.iocoder.yudao.module.infra.dal.mysql.file.MultipartUploadSessionMapper;
import cn.iocoder.yudao.module.infra.service.minio.MinioService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.UUID;

/**
 * 分片上传服务实现类
 *
 * @author AI Assistant
 */
@Service
@Slf4j
public class MultipartUploadServiceImpl implements MultipartUploadService {

    @Resource
    private MultipartUploadSessionMapper multipartUploadSessionMapper;

    @Resource
    private MinioService minioService;

    @Override
    @Transactional
    public MultipartUploadInitResponse initMultipartUpload(MultipartUploadInitRequest request) {
        // 1. 获取当前用户ID
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        if (userId == null) {
            throw ServiceExceptionUtil.exception(400, "用户未登录");
        }

        // 2. 生成uploadId和objectName
        String uploadId = UUID.randomUUID().toString();
        String objectName = String.format("user_%d/%s_%s", userId, UUID.randomUUID(),
                                         sanitizeFilename(request.getFileName()));

        // 3. 计算总分片数
        Integer chunkSize = request.getChunkSize();
        if (chunkSize == null || chunkSize <= 0) {
            chunkSize = 200 * 1024 * 1024; // 默认500MB
        }
        Integer totalChunks = (int) Math.ceil((double) request.getFileSize() / chunkSize);

        // 4. 设置过期时间（1小时）
        LocalDateTime expiresAt = LocalDateTime.now().plusHours(1);

        // 5. 初始化MinIO分片上传
        try {
            minioService.initMultipartUpload(uploadId, objectName);
        } catch (Exception e) {
            log.error("初始化MinIO分片上传失败", e);
            throw ServiceExceptionUtil.exception(500, "初始化分片上传失败: " + e.getMessage());
        }

        // 6. 保存会话信息到数据库
        MultipartUploadSessionDO sessionDO = MultipartUploadSessionDO.builder()
                .uploadId(uploadId)
                .objectName(objectName)
                .fileSize(request.getFileSize())
                .chunkSize(chunkSize)
                .totalChunks(totalChunks)
                .fileName(request.getFileName())
                .fileType(request.getFileType())
                .userId(userId)
                .expiresAt(expiresAt)
                .status(0) // 进行中
                .build();
        multipartUploadSessionMapper.insert(sessionDO);

        // 7. 构建响应
        MultipartUploadInitResponse response = new MultipartUploadInitResponse();
        response.setUploadId(uploadId);
        response.setObjectName(objectName);
        response.setChunkSize(chunkSize);
        response.setTotalChunks(totalChunks);
        response.setExpires(3600); // 1小时
        response.setExpiresAt(expiresAt);

        return response;
    }

    @Override
    public ChunkPresignedUrlResponse getChunkPresignedUrl(String uploadId, Integer partNumber, Integer expires) {
        // 1. 校验参数
        if (uploadId == null || uploadId.trim().isEmpty()) {
            throw ServiceExceptionUtil.exception(400, "上传会话ID不能为空");
        }
        if (partNumber == null || partNumber < 1) {
            throw ServiceExceptionUtil.exception(400, "分片编号必须大于0");
        }
        if (expires == null || expires <= 0) {
            expires = 900; // 默认15分钟
        }

        // 2. 查询会话信息
        MultipartUploadSessionDO session = multipartUploadSessionMapper.selectByUploadId(uploadId);
        if (session == null) {
            throw ServiceExceptionUtil.exception(404, "上传会话不存在");
        }

        // 3. 检查会话是否过期
        if (LocalDateTime.now().isAfter(session.getExpiresAt())) {
            throw ServiceExceptionUtil.exception(410, "上传会话已过期");
        }

        // 4. 检查分片编号是否有效
        if (partNumber > session.getTotalChunks()) {
            throw ServiceExceptionUtil.exception(400, "分片编号超出范围");
        }

        // 5. 生成预签名URL
        String presignedUrl;
        try {
            presignedUrl = minioService.generateChunkPresignedUrl(uploadId, session.getObjectName(), partNumber, expires);
        } catch (Exception e) {
            log.error("生成分片预签名URL失败", e);
            throw ServiceExceptionUtil.exception(500, "生成预签名URL失败: " + e.getMessage());
        }

        // 6. 构建响应
        ChunkPresignedUrlResponse response = new ChunkPresignedUrlResponse();
        response.setUploadId(uploadId);
        response.setPartNumber(partNumber);
        response.setPresignedUrl(presignedUrl);
        response.setExpires(expires);
        response.setExpiresAt(LocalDateTime.now().plusSeconds(expires));

        return response;
    }

    @Override
    @Transactional
    public MultipartUploadCompleteResponse completeMultipartUpload(MultipartUploadCompleteRequest request) {
        // 1. 校验参数
        if (request.getUploadId() == null || request.getUploadId().trim().isEmpty()) {
            throw ServiceExceptionUtil.exception(400, "上传会话ID不能为空");
        }
        if (request.getParts() == null || request.getParts().isEmpty()) {
            throw ServiceExceptionUtil.exception(400, "分片信息不能为空");
        }

        // 2. 查询会话信息
        MultipartUploadSessionDO session = multipartUploadSessionMapper.selectByUploadId(request.getUploadId());
        if (session == null) {
            throw ServiceExceptionUtil.exception(404, "上传会话不存在");
        }

        // 3. 检查会话是否过期
        if (LocalDateTime.now().isAfter(session.getExpiresAt())) {
            throw ServiceExceptionUtil.exception(410, "上传会话已过期");
        }

        // 4. 验证分片完整性
        if (request.getParts().size() != session.getTotalChunks()) {
            throw ServiceExceptionUtil.exception(400, "分片数量不匹配");
        }

        // 5. 完成MinIO分片上传
        String fileUrl;
        String etag;
        try {
            MinioService.MultipartUploadCompleteResult result = minioService.completeMultipartUpload(request.getUploadId(), session.getObjectName(), request.getParts());
            fileUrl = result.getFileUrl();
            etag = result.getEtag();
        } catch (Exception e) {
            log.error("完成MinIO分片上传失败", e);
            throw ServiceExceptionUtil.exception(500, "完成分片上传失败: " + e.getMessage());
        }

        // 6. 更新会话状态
        session.setStatus(1); // 已完成
        multipartUploadSessionMapper.updateById(session);

        // 7. 构建响应
        MultipartUploadCompleteResponse response = new MultipartUploadCompleteResponse();
        response.setObjectName(session.getObjectName());
        response.setFileUrl(fileUrl);
        response.setFileSize(session.getFileSize());
        response.setEtag(etag);

        return response;
    }

    @Override
    @Transactional
    public void abortMultipartUpload(String uploadId) {
        // 1. 校验参数
        if (uploadId == null || uploadId.trim().isEmpty()) {
            throw ServiceExceptionUtil.exception(400, "上传会话ID不能为空");
        }

        // 2. 查询会话信息
        MultipartUploadSessionDO session = multipartUploadSessionMapper.selectByUploadId(uploadId);
        if (session == null) {
            throw ServiceExceptionUtil.exception(404, "上传会话不存在");
        }

        // 3. 取消MinIO分片上传
        try {
            minioService.abortMultipartUpload(uploadId, session.getObjectName());
        } catch (Exception e) {
            log.error("取消MinIO分片上传失败", e);
            throw ServiceExceptionUtil.exception(500, "取消分片上传失败: " + e.getMessage());
        }

        // 4. 更新会话状态
        session.setStatus(2); // 已取消
        multipartUploadSessionMapper.updateById(session);
    }

    /**
     * 文件名安全处理
     */
    private String sanitizeFilename(String fileName) {
        if (fileName == null || fileName.trim().isEmpty()) {
            return "unknown";
        }
        return fileName.replaceAll("[^a-zA-Z0-9.-]", "_");
    }

}
