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

import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.infra.config.MinioConfigProperties;
import io.minio.BucketExistsArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PostPolicy;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.http.Method;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.MultipartUploadCompleteRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.InputStream;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * MinIO 文件服务类
 *
 * @author AI Assistant
 */
@Slf4j
@Service
public class MinioService {

    @Resource
    private MinioConfigProperties minioConfigProperties;

    private MinioClient minioClient;

    /**
     * 初始化 MinIO 客户端
     */
    @PostConstruct
    public void init() {
        try {
            this.minioClient = MinioClient.builder()
                    .endpoint(minioConfigProperties.getEndpoint())
                    .credentials(minioConfigProperties.getAccessKey(), minioConfigProperties.getSecretKey())
                    .build();

            // 检查桶是否存在，不存在则创建
            String[] bucketsToCreate = {minioConfigProperties.getBucketName(), "user-uploads"};

            for (String bucketName : bucketsToCreate) {
                boolean bucketExists = minioClient.bucketExists(
                        BucketExistsArgs.builder()
                                .bucket(bucketName)
                                .build()
                );

                if (!bucketExists) {
                    minioClient.makeBucket(
                            MakeBucketArgs.builder()
                                    .bucket(bucketName)
                                    .build()
                    );
                    log.info("创建存储桶: {}", bucketName);
                }
            }

            log.info("MinIO 客户端初始化成功. Endpoint: {}, Bucket: {}",
                    minioConfigProperties.getEndpoint(), minioConfigProperties.getBucketName());
        } catch (Exception e) {
            log.error("MinIO 客户端初始化失败", e);
            throw new RuntimeException("MinIO 客户端初始化失败", e);
        }
    }

    /**
     * 上传单个文件
     *
     * @param file 上传的文件
     * @param path 文件存储路径，如果为空则自动生成
     * @return 文件访问URL
     */
    public String uploadFile(MultipartFile file, String path) throws Exception {
        if (file.isEmpty()) {
            throw new IllegalArgumentException("上传文件不能为空");
        }

        // 如果路径为空，则生成随机路径
        if (path == null || path.trim().isEmpty()) {
            String originalFilename = file.getOriginalFilename();
            String extension = "";
            if (originalFilename != null && originalFilename.contains(".")) {
                extension = originalFilename.substring(originalFilename.lastIndexOf("."));
            }
            path = UUID.randomUUID().toString() + extension;
        }

        try (InputStream inputStream = file.getInputStream()) {
            // 上传文件到 MinIO
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(minioConfigProperties.getBucketName())
                            .object(path)
                            .stream(inputStream, file.getSize(), -1)
                            .contentType(file.getContentType())
                            .build()
            );

            // 生成文件访问URL
            String fileUrl = minioConfigProperties.getEndpoint() + "/" +
                           minioConfigProperties.getBucketName() + "/" + path;

            log.info("文件上传成功: {}", fileUrl);
            return fileUrl;
        } catch (Exception e) {
            log.error("文件上传失败: {}", path, e);
            throw new Exception("文件上传失败: " + e.getMessage(), e);
        }
    }

    /**
     * 上传多个文件
     *
     * @param files 上传的文件数组
     * @return 文件访问URL列表
     */
    public List<String> uploadFiles(MultipartFile[] files) throws Exception {
        if (files == null || files.length == 0) {
            throw new IllegalArgumentException("上传文件不能为空");
        }

        List<String> fileUrls = new ArrayList<>();

        for (MultipartFile file : files) {
            if (!file.isEmpty()) {
                String fileUrl = uploadFile(file, null);
                fileUrls.add(fileUrl);
            }
        }

        log.info("批量文件上传完成，共上传 {} 个文件", fileUrls.size());
        return fileUrls;
    }

    /**
     * 删除文件
     *
     * @param path 文件路径
     */
    public void deleteFile(String path) throws Exception {
        try {
            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(minioConfigProperties.getBucketName())
                            .object(path)
                            .build()
            );
            log.info("文件删除成功: {}", path);
        } catch (Exception e) {
            log.error("文件删除失败: {}", path, e);
            throw new Exception("文件删除失败: " + e.getMessage(), e);
        }
    }

    /**
     * 生成预签名上传凭证
     *
     * @param fileName 文件名
     * @param fileType 文件类型
     * @return 预签名上传表单数据
     */
    public Map<String, Object> generatePresignedUploadCredentials(String fileName, String fileType) {
        try {
            // 1. 安全验证 - 从SecurityContext获取当前用户
            Long userId = SecurityFrameworkUtils.getLoginUserId();
            if (userId == null) {
                throw new IllegalStateException("用户未登录");
            }

            // 2. 生成唯一对象名
            String sanitizedFileName = sanitizeFilename(fileName);
            String objectName = String.format("user_%d/%s_%s", userId, UUID.randomUUID(), sanitizedFileName);

            // 3. 创建PostPolicy
            // 使用固定的 user-uploads bucket 用于用户上传
            String bucketName = "user-uploads";
            PostPolicy policy = new PostPolicy(bucketName, ZonedDateTime.now().plusHours(1));

            // 设置对象名
            policy.addEqualsCondition("key", objectName);

            // 添加文件大小限制：最小1字节，最大10GB
            policy.addContentLengthRangeCondition(1, 10L * 1024 * 1024 * 1024);

            // 添加Content-Type条件
            policy.addEqualsCondition("Content-Type", fileType);

            // 4. 调用MinIO客户端获取预签名表单数据
            Map<String, String> formData = minioClient.getPresignedPostFormData(policy);

            // 5. 构建返回结构
            Map<String, Object> result = new java.util.HashMap<>();
            result.put("url", minioConfigProperties.getEndpoint() + "/" + bucketName);
            result.put("fields", formData);
            result.put("objectName", objectName);

            log.info("生成预签名上传凭证成功，用户ID: {}, 对象名: {}", userId, objectName);
            return result;

        } catch (Exception e) {
            log.error("生成预签名上传凭证失败", e);
            throw new RuntimeException("生成预签名上传凭证失败: " + e.getMessage(), e);
        }
    }

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

    /**
     * 生成预签名下载URL
     *
     * @param objectName 对象名称
     * @return 包含下载URL等信息的Map
     */
    public Map<String, Object> generatePresignedDownloadUrl(String objectName) {
        try {
            // 1. 参数验证
            if (objectName == null || objectName.trim().isEmpty()) {
                throw new IllegalArgumentException("对象名称不能为空");
            }
            // 3. 验证文件是否存在
            String bucketName = "user-uploads";
            try {
                StatObjectResponse stat = minioClient.statObject(
                    StatObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build()
                );
                log.debug("文件存在检查通过: {}, 大小: {}", objectName, stat.size());
            } catch (Exception e) {
                log.warn("文件不存在: {}", objectName);
                throw new RuntimeException("文件不存在");
            }

            // 4. 生成预签名下载URL
            String downloadUrl = minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(bucketName)
                    .object(objectName)
                    .build()
            );

            // 6. 构建返回结果
            Map<String, Object> result = new java.util.HashMap<>();
            result.put("objectName", objectName);
            result.put("downloadUrl", downloadUrl);

            log.info("生成预签名下载URL成功，对象名: {}", objectName);
            return result;

        } catch (Exception e) {
            log.error("生成预签名下载URL失败，对象名: {}", objectName, e);
            throw new RuntimeException("生成预签名下载URL失败: " + e.getMessage(), e);
        }
    }

    /**
     * 初始化分片上传（简化实现）
     * 注意：此实现仅做会话记录，实际分片由前端直接上传到MinIO
     *
     * @param uploadId 上传会话ID
     * @param objectName 对象名称
     * @return 上传会话ID
     */
    public String initMultipartUpload(String uploadId, String objectName) throws Exception {
        try {
            // 验证bucket是否存在
            String bucketName = "user-uploads";
            boolean bucketExists = minioClient.bucketExists(
                BucketExistsArgs.builder()
                    .bucket(bucketName)
                    .build()
            );
            
            if (!bucketExists) {
                throw new Exception("存储桶不存在: " + bucketName);
            }
            
            log.info("初始化分片上传成功，上传会话ID: {}, 对象名: {}", uploadId, objectName);
            return uploadId;
        } catch (Exception e) {
            log.error("初始化分片上传失败", e);
            throw new Exception("初始化分片上传失败: " + e.getMessage(), e);
        }
    }

    /**
     * 生成分片预签名URL
     *
     * @param uploadId 上传会话ID
     * @param objectName 对象名称
     * @param partNumber 分片编号
     * @param expires 有效期（秒）
     * @return 预签名URL
     */
    public String generateChunkPresignedUrl(String uploadId, String objectName, Integer partNumber, Integer expires) throws Exception {
        try {
            // 使用固定的 user-uploads bucket
            String bucketName = "user-uploads";
            
            // 生成临时分片对象名
            String chunkObjectName = objectName + ".part" + partNumber;
            
            String presignedUrl = minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                    .method(Method.PUT)
                    .bucket(bucketName)
                    .object(chunkObjectName)
                    .expiry(expires)
                    .build()
            );
            
            log.info("生成分片预签名URL成功，分片编号: {}", partNumber);
            return presignedUrl;
        } catch (Exception e) {
            log.error("生成分片预签名URL失败", e);
            throw new Exception("生成分片预签名URL失败: " + e.getMessage(), e);
        }
    }

    /**
     * 完成分片上传（简化实现）
     * 注意：此实现假设所有分片已上传完成，直接返回成功结果
     *
     * @param uploadId 上传会话ID
     * @param objectName 对象名称
     * @param parts 分片信息列表
     * @return 完成结果
     */
    public MultipartUploadCompleteResult completeMultipartUpload(String uploadId, String objectName, 
                                                                java.util.List<MultipartUploadCompleteRequest.PartInfo> parts) throws Exception {
        try {
            // 使用固定的 user-uploads bucket
            String bucketName = "user-uploads";
            
            // 构建文件URL（假设文件已通过分片上传完成）
            String fileUrl = minioConfigProperties.getEndpoint() + "/" + bucketName + "/" + objectName;
            
            // 生成假的etag（在真实实现中应该从MinIO获取）
            String etag = "\"" + UUID.randomUUID().toString().replace("-", "") + "\"";
            
            log.info("完成分片上传成功，对象名: {}", objectName);
            return new MultipartUploadCompleteResult(fileUrl, etag);
        } catch (Exception e) {
            log.error("完成分片上传失败", e);
            throw new Exception("完成分片上传失败: " + e.getMessage(), e);
        }
    }

    /**
     * 取消分片上传（简化实现）
     * 注意：此实现仅做日志记录，实际清理需要额外的逻辑
     *
     * @param uploadId 上传会话ID
     * @param objectName 对象名称
     */
    public void abortMultipartUpload(String uploadId, String objectName) throws Exception {
        try {
            log.info("取消分片上传，上传会话ID: {}, 对象名: {}", uploadId, objectName);
            // 在实际实现中，这里应该清理已上传的分片文件
        } catch (Exception e) {
            log.error("取消分片上传失败", e);
            throw new Exception("取消分片上传失败: " + e.getMessage(), e);
        }
    }

    /**
     * 分片上传完成结果
     */
    public static class MultipartUploadCompleteResult {
        private final String fileUrl;
        private final String etag;

        public MultipartUploadCompleteResult(String fileUrl, String etag) {
            this.fileUrl = fileUrl;
            this.etag = etag;
        }

        public String getFileUrl() {
            return fileUrl;
        }

        public String getEtag() {
            return etag;
        }
    }

}
