package com.vetti.common.utils.file;

import com.vetti.common.core.domain.MinioFileInfo;
import com.vetti.common.utils.DateUtils;
import io.minio.*;
import io.minio.errors.ErrorResponseException;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.log4j.Log4j2;
import org.springframework.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Log4j2
@Component
public class MinioUtil {

    // 默认预签名URL有效期：7天（单位：秒）
    private static final int DEFAULT_EXPIRES = 60 * 60 * 24 * 7;

    // 默认最大文件大小：100MB
    @Value("${fs.minio.max-file-size}")
    private long maxFileSize;

    @Autowired
    private MinioClient minioClient;

    @Value("${fs.minio.endpoint}")
    private String endpoint;

    /**
     * 获取所有存储桶名称
     *
     * @return 存储桶名称列表
     */
    public List<String> getAllBucketNames() {
        List<String> bucketNames = new ArrayList<>();
        try {
            // 获取所有桶信息
            List<Bucket> buckets = minioClient.listBuckets();

            // 提取桶名称
            for (Bucket bucket : buckets) {
                bucketNames.add(bucket.name());
            }

            log.info("成功获取所有存储桶名称，共{}个", bucketNames.size());
        } catch (Exception e) {
            log.error("获取存储桶列表失败", e);
            throw new RuntimeException("获取存储桶列表失败: " + e.getMessage());
        }
        return bucketNames;
    }

    /**
     * 检查存储桶是否存在，不存在则创建
     */
    public void checkAndCreateBucket(String bucketName) {
        if (StringUtils.isEmpty(bucketName)) {
            throw new IllegalArgumentException("存储桶名称不能为空");
        }
        try {
            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!exists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                log.info("存储桶[{}]创建成功", bucketName);

                // 3. 配置桶策略为 "public-read"（核心：允许匿名读）
                // 策略语句：允许所有用户（*）对桶内所有文件（/*）执行读操作（s3:GetObject）
                String policyJson = "{\n" +
                        "  \"Version\": \"2012-10-17\",\n" +
                        "  \"Statement\": [\n" +
                        "    {\n" +
                        "      \"Effect\": \"Allow\",\n" +
                        "      \"Principal\": \"*\",\n" +
                        "      \"Action\": \"s3:GetObject\",\n" +
                        "      \"Resource\": \"arn:aws:s3:::" + bucketName + "/*\"\n" +
                        "    }\n" +
                        "  ]\n" +
                        "}";

                // 应用策略到桶
                minioClient.setBucketPolicy(SetBucketPolicyArgs.builder()
                        .bucket(bucketName)
                        .config(policyJson)
                        .build());
                log.info("桶策略配置成功，现在文件可匿名访问");
            } else {
                log.info("存储桶[{}]已存在", bucketName);
            }
        } catch (Exception e) {
            log.error("检查或创建存储桶[{}]失败", bucketName, e);
            throw new RuntimeException("操作存储桶失败: " + e.getMessage());
        }
    }

    public String objectName(Long loginUserId) {
        DateUtils.parseDateToStr(DateUtils.YYYYMMDD, DateUtils.getNowDate());
        return loginUserId + "/" + DateUtils.parseDateToStr(DateUtils.YYYYMMDD, DateUtils.getNowDate()) + "/";
    }

    /**
     * 上传文件
     *
     * @param file       上传的文件
     * @param objectName 存储在 MinIO 中的文件名（可包含路径，如 "images/xxx.jpg"）
     * @param bucketName 存储桶名称
     * @return 文件访问 URL
     */
    public String uploadFile(MultipartFile file, String objectName, String bucketName) {
        // 参数校验
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("上传文件不能为空");
        }
        if (file.getSize() > maxFileSize) {
            throw new IllegalArgumentException("文件大小超过限制，最大支持" + maxFileSize / (1024 * 1024) + "MB");
        }
        if (StringUtils.isEmpty(objectName)) {
            throw new IllegalArgumentException("存储文件名不能为空");
        }
        if (StringUtils.isEmpty(bucketName)) {
            throw new IllegalArgumentException("存储桶名称不能为空");
        }
        objectName = objectName + System.currentTimeMillis() + "." + FileTypeUtils.getFileType(file.getOriginalFilename());
        try {
            // 检查存储桶
            checkAndCreateBucket(bucketName);

            // 上传文件
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(file.getInputStream(), file.getSize(), -1)
                            .contentType(file.getContentType())
                            .build()
            );

            String fileUrl = String.format("%s/%s/%s", endpoint, bucketName, objectName);
            log.info("文件[{}]上传成功，访问地址：{}", objectName, fileUrl);
            return objectName;
        } catch (Exception e) {
            log.error("文件[{}]上传失败", objectName, e);
            throw new RuntimeException("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * 上传文件（带自定义元数据）
     */
    public String uploadFileWithMetadata(MultipartFile file, String objectName, String bucketName, Map<String, String> metadata) {
        // 参数校验
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("上传文件不能为空");
        }
        if (file.getSize() > maxFileSize) {
            throw new IllegalArgumentException("文件大小超过限制，最大支持" + maxFileSize / (1024 * 1024) + "MB");
        }
        if (StringUtils.isEmpty(objectName)) {
            throw new IllegalArgumentException("存储文件名不能为空");
        }
        if (StringUtils.isEmpty(bucketName)) {
            throw new IllegalArgumentException("存储桶名称不能为空");
        }

        try {
            // 检查存储桶
            checkAndCreateBucket(bucketName);

            // 构建上传参数
            PutObjectArgs.Builder builder = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .contentType(file.getContentType());

            // 添加元数据 - 修正为使用userMetadata方法
            if (metadata != null && !metadata.isEmpty()) {
                builder.userMetadata(metadata);
            }

            // 上传文件
            minioClient.putObject(builder.build());

            String fileUrl = String.format("%s/%s/%s", endpoint, bucketName, objectName);
            log.info("文件[{}]上传成功，访问地址：{}", objectName, fileUrl);
            return fileUrl;
        } catch (Exception e) {
            log.error("文件[{}]上传失败", objectName, e);
            throw new RuntimeException("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * 下载文件
     *
     * @param objectName 存储在 MinIO 中的文件名
     * @param response   响应对象
     * @param bucketName 存储桶名称
     */
    public void downloadFile(String objectName, HttpServletResponse response, String bucketName) {
        if (StringUtils.isEmpty(objectName)) {
            throw new IllegalArgumentException("文件名称不能为空");
        }
        if (StringUtils.isEmpty(bucketName)) {
            throw new IllegalArgumentException("存储桶名称不能为空");
        }
        if (response == null) {
            throw new IllegalArgumentException("响应对象不能为空");
        }

        try (InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
             OutputStream os = response.getOutputStream()) {

            // 获取文件名
            String fileName = objectName.substring(objectName.lastIndexOf("/") + 1);
            String encodedFileName = URLEncoder.encode(fileName, "UTF-8");

            // 设置响应头
            response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
            response.setHeader("Content-Type", "application/octet-stream");
            response.setHeader("Content-Length", String.valueOf(stream.available()));

            // 写入响应
            byte[] buffer = new byte[1024];
            int len;
            while ((len = stream.read(buffer)) > 0) {
                os.write(buffer, 0, len);
            }
            os.flush();
            log.info("文件[{}]下载成功", objectName);
        } catch (Exception e) {
            log.error("文件[{}]下载失败", objectName, e);
            throw new RuntimeException("文件下载失败: " + e.getMessage());
        }
    }

    /**
     * 删除文件
     *
     * @param objectName 存储在 MinIO 中的文件名
     * @param bucketName 存储桶名称
     */
    public void deleteFile(String objectName, String bucketName) {
        if (StringUtils.isEmpty(objectName)) {
            throw new IllegalArgumentException("文件名称不能为空");
        }
        if (StringUtils.isEmpty(bucketName)) {
            throw new IllegalArgumentException("存储桶名称不能为空");
        }

        try {
            if (fileExists(objectName, bucketName)) {
                minioClient.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(bucketName)
                                .object(objectName)
                                .build()
                );
                log.info("文件[{}]删除成功", objectName);
            } else {
                log.warn("文件[{}]不存在，无需删除", objectName);
            }
        } catch (Exception e) {
            log.error("文件[{}]删除失败", objectName, e);
            throw new RuntimeException("文件删除失败: " + e.getMessage());
        }
    }

    /**
     * 批量删除文件
     *
     * @param objectNames 存储在 MinIO 中的文件名列表
     * @param bucketName  存储桶名称
     */
    public void deleteFiles(List<String> objectNames, String bucketName) {
        if (objectNames == null || objectNames.isEmpty()) {
            log.warn("批量删除的文件列表为空，无需操作");
            return;
        }
        if (StringUtils.isEmpty(bucketName)) {
            throw new IllegalArgumentException("存储桶名称不能为空");
        }

        try {
            List<DeleteObject> deleteObjects = new ArrayList<>();
            for (String objectName : objectNames) {
                if (StringUtils.hasText(objectName)) {
                    deleteObjects.add(new DeleteObject(objectName));
                }
            }

            if (!deleteObjects.isEmpty()) {
                Iterable<Result<DeleteError>> results = minioClient.removeObjects(
                        RemoveObjectsArgs.builder()
                                .bucket(bucketName)
                                .objects(deleteObjects)
                                .build()
                );

                // 处理删除结果
                for (Result<DeleteError> result : results) {
                    DeleteError error = result.get();
                    log.error("文件[{}]删除失败: {}", error.objectName(), error.message());
                }
                log.info("批量删除完成，共处理{}个文件", deleteObjects.size());
            }
        } catch (Exception e) {
            log.error("批量删除文件失败", e);
            throw new RuntimeException("批量删除文件失败: " + e.getMessage());
        }
    }

    /**
     * 获取存储桶内所有文件信息列表
     */
    public List<MinioFileInfo> listFiles(String bucketName) {
        if (StringUtils.isEmpty(bucketName)) {
            throw new IllegalArgumentException("存储桶名称不能为空");
        }

        List<MinioFileInfo> fileList = new ArrayList<>();
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .recursive(true)
                            .build()
            );

            for (Result<Item> result : results) {
                Item item = result.get();
                MinioFileInfo fileInfo = new MinioFileInfo();
                fileInfo.setFileName(item.objectName());
                fileInfo.setSize(item.size());
                fileInfo.setLastModified(item.lastModified());
                fileInfo.setDirectory(item.isDir());
                fileList.add(fileInfo);
            }
            log.info("获取存储桶[{}]文件列表成功，共{}个文件", bucketName, fileList.size());
        } catch (Exception e) {
            log.error("获取存储桶[{}]文件列表失败", bucketName, e);
            throw new RuntimeException("获取文件列表失败: " + e.getMessage());
        }
        return fileList;
    }

    /**
     * 获取文件临时访问 URL（使用默认有效期7天）
     */
    public String getPresignedUrl(String objectName, String bucketName) {
        return getPresignedUrl(objectName, DEFAULT_EXPIRES, bucketName);
    }

    /**
     * 获取文件临时访问 URL
     *
     * @param objectName 存储在 MinIO 中的文件名
     * @param expires    有效期（秒）
     * @param bucketName 存储桶名称
     * @return 临时访问 URL
     */
    public String getPresignedUrl(String objectName, int expires, String bucketName) {
        if (StringUtils.isEmpty(objectName)) {
            throw new IllegalArgumentException("文件名称不能为空");
        }
        if (expires <= 0) {
            throw new IllegalArgumentException("有效期必须大于0");
        }
        if (StringUtils.isEmpty(bucketName)) {
            throw new IllegalArgumentException("存储桶名称不能为空");
        }

        try {
            if (!fileExists(objectName, bucketName)) {
                log.warn("文件[{}]不存在，无法生成预签名URL", objectName);
                return null;
            }

            String url = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(expires)
                            .build()
            );
            log.info("文件[{}]预签名URL生成成功，有效期{}秒", objectName, expires);
            return url;
        } catch (Exception e) {
            log.error("文件[{}]预签名URL生成失败", objectName, e);
            throw new RuntimeException("生成预签名URL失败: " + e.getMessage());
        }
    }

    /**
     * 获取预签名URL（支持不同HTTP方法）
     */
    public String getPresignedUrl(String objectName, int expires, String bucketName, Method method) {
        if (method == null) {
            throw new IllegalArgumentException("HTTP方法不能为空");
        }
        if (StringUtils.isEmpty(objectName)) {
            throw new IllegalArgumentException("文件名称不能为空");
        }
        if (expires <= 0) {
            throw new IllegalArgumentException("有效期必须大于0");
        }
        if (StringUtils.isEmpty(bucketName)) {
            throw new IllegalArgumentException("存储桶名称不能为空");
        }

        try {
            String url = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(method)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(expires)
                            .build()
            );
            log.info("文件[{}]预签名URL({})生成成功，有效期{}秒", objectName, method, expires);
            return url;
        } catch (Exception e) {
            log.error("文件[{}]预签名URL生成失败", objectName, e);
            throw new RuntimeException("生成预签名URL失败: " + e.getMessage());
        }
    }

    /**
     * 检查文件是否存在
     */
    public boolean fileExists(String objectName, String bucketName) {
        if (StringUtils.isEmpty(objectName)) {
            return false;
        }
        if (StringUtils.isEmpty(bucketName)) {
            return false;
        }

        try {
            minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()
            );
            return true;
        } catch (ErrorResponseException e) {
            // 404错误表示文件不存在
            if (e.errorResponse().code().equals("NoSuchKey")) {
                return false;
            }
            log.error("检查文件[{}]存在性时发生错误", objectName, e);
            return false;
        } catch (Exception e) {
            log.error("检查文件[{}]存在性时发生错误", objectName, e);
            return false;
        }
    }

    /**
     * 复制文件
     *
     * @param sourceBucketName 源存储桶
     * @param sourceObjectName 源文件名称
     * @param targetBucketName 目标存储桶
     * @param targetObjectName 目标文件名称
     */
    public void copyObject(String sourceBucketName, String sourceObjectName,
                           String targetBucketName, String targetObjectName) {
        if (StringUtils.isEmpty(sourceBucketName) || StringUtils.isEmpty(sourceObjectName) ||
                StringUtils.isEmpty(targetBucketName) || StringUtils.isEmpty(targetObjectName)) {
            throw new IllegalArgumentException("复制文件参数不能为空");
        }

        try {
            // 检查源文件是否存在
            if (!fileExists(sourceObjectName, sourceBucketName)) {
                throw new RuntimeException("源文件不存在: " + sourceObjectName);
            }

            // 检查目标存储桶
            checkAndCreateBucket(targetBucketName);

            // 执行复制
            minioClient.copyObject(
                    CopyObjectArgs.builder()
                            .source(
                                    CopySource.builder()
                                            .bucket(sourceBucketName)
                                            .object(sourceObjectName)
                                            .build()
                            )
                            .bucket(targetBucketName)
                            .object(targetObjectName)
                            .build()
            );
            log.info("文件复制成功: {}/{} -> {}/{}",
                    sourceBucketName, sourceObjectName,
                    targetBucketName, targetObjectName);
        } catch (Exception e) {
            log.error("文件复制失败", e);
            throw new RuntimeException("文件复制失败: " + e.getMessage());
        }
    }

    /**
     * 移动文件（本质是复制后删除原文件）
     */
    public void moveObject(String sourceBucketName, String sourceObjectName,
                           String targetBucketName, String targetObjectName) {
        try {
            // 先复制
            copyObject(sourceBucketName, sourceObjectName, targetBucketName, targetObjectName);
            // 再删除原文件
            deleteFile(sourceObjectName, sourceBucketName);
            log.info("文件移动成功: {}/{} -> {}/{}",
                    sourceBucketName, sourceObjectName,
                    targetBucketName, targetObjectName);
        } catch (Exception e) {
            log.error("文件移动失败", e);
            throw new RuntimeException("文件移动失败: " + e.getMessage());
        }
    }

    /**
     * 创建模拟文件夹（MinIO中实际是创建一个以/结尾的空对象）
     */
    public void createDirectory(String directoryName, String bucketName) {
        if (StringUtils.isEmpty(directoryName)) {
            throw new IllegalArgumentException("文件夹名称不能为空");
        }
        if (StringUtils.isEmpty(bucketName)) {
            throw new IllegalArgumentException("存储桶名称不能为空");
        }

        // 确保文件夹名称以/结尾
        if (!directoryName.endsWith("/")) {
            directoryName += "/";
        }

        try {
            // 检查文件夹是否已存在
            if (fileExists(directoryName, bucketName)) {
                log.info("文件夹[{}]已存在", directoryName);
                return;
            }

            // 创建空对象模拟文件夹
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(directoryName)
                            .stream(null, 0, -1)
                            .build()
            );
            log.info("文件夹[{}]创建成功", directoryName);
        } catch (Exception e) {
            log.error("文件夹[{}]创建失败", directoryName, e);
            throw new RuntimeException("创建文件夹失败: " + e.getMessage());
        }
    }

    /**
     * 获取文件信息
     */
    public MinioFileInfo getFileInfo(String objectName, String bucketName) {
        if (StringUtils.isEmpty(objectName) || StringUtils.isEmpty(bucketName)) {
            return null;
        }

        try {
            StatObjectResponse stat = minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()
            );

            MinioFileInfo fileInfo = new MinioFileInfo();
            fileInfo.setFileName(objectName);
            fileInfo.setSize(stat.size());
            fileInfo.setLastModified(stat.lastModified());
            fileInfo.setContentType(stat.contentType());
            fileInfo.setDirectory(false);
            fileInfo.setMetadata(stat.userMetadata());

            return fileInfo;
        } catch (Exception e) {
            log.error("获取文件[{}]信息失败", objectName, e);
            return null;
        }
    }
}