package com.bonus.file.utils;

import com.bonus.file.config.MinioConfig;
import com.bonus.file.entity.FileDetails;
import com.bonus.system.api.domain.SysFile;
import io.minio.*;
import io.minio.errors.MinioException;
import io.minio.http.Method;
import io.minio.messages.Item;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * MinioUtil 工具类
 * 封装 MinIO 常用操作，如文件上传、下载、删除、复制等功能
 * @author bonus
 */
@Component
public class MinioUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(MinioUtil.class);

    @Resource
    private MinioClient minioClient;

    @Resource
    private MinioConfig minioConfig;

    /**
     * i皖送，minio上传文件域名路径
     */
    public String domain = "http://sgwpdm.ah.sgcc.com.cn/iws/ahjiju/";
    /**
     * 分片大小
     */
    private static final long PART_SIZE = 5*1024*1024;
    /**
     * 初始化默认存储桶
     * 在 Spring 容器启动后自动调用，检查默认存储桶是否存在，若不存在则创建
     */
//    @PostConstruct
    public void init() {
        try {
            if (bucketExists(minioConfig.getBucketName())) {
                LOGGER.error("Bucket already exists: {}", minioConfig.getBucketName());
            } else {
                createBucket(minioConfig.getBucketName());
            }
        } catch (Exception e) {
            LOGGER.error("Error initializing default bucket: {}",e.getMessage(),e);
            throw new RuntimeException("Error initializing default bucket: " + e.getMessage(), e);
        }
    }

    /**
     * 检查指定存储桶是否存在
     * @param bucketName 存储桶名称
     * @return true 表示存在，false 表示不存在
     * @throws Exception 若检查过程中发生异常
     */
    public boolean bucketExists(String bucketName) throws Exception {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建指定的存储桶
     * @param bucketName 存储桶名称
     * @throws Exception 若创建过程中发生异常
     */
    public void createBucket(String bucketName) throws Exception {
        if (bucketExists(bucketName)) {
            LOGGER.error("Bucket already exists: {}", bucketName);
        } else {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            LOGGER.error("Bucket created successfully: {}", bucketName);
        }
    }

    /**
     *
     * 上传文件到指定存储桶
     * @param file MultipartFile 文件对象
     * @return 上传后的文件访问 URL
     * @throws Exception 若上传过程中发生异常
     */
    public SysFile uploadFile(MultipartFile file, String folderPath) throws Exception {

        if (file.getSize() < 10 * 1024 * 1024L) {
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(folderPath)
                    // -1 表示不限制文件大小
                    .stream(file.getInputStream(), file.getInputStream().available(), -1)
                    .contentType(file.getContentType())
                    .build());
        } else {
            uploadLargeFile(folderPath, file);
        }
        String url = domain + folderPath;
        return SysFile.builder()
                .name(file.getOriginalFilename())
                .url(url).build();
    }

    /**
     * 分片上传文件到指定文件夹
     * @param file MultipartFile 文件对象
     * @param folderPath 目标文件夹路径（如 "folder/subfolder"）
     * @throws Exception 若上传过程中发生异常
     */
    public void uploadLargeFile(String folderPath,MultipartFile file) throws Exception {
        long fileSize = file.getSize();
        int partCount = (int) Math.ceil((double) fileSize / PART_SIZE);

        List<String> partNames = new ArrayList<>();
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);

        List<CompletableFuture<Void>> futures = new ArrayList<>();

        // 上传每个分片
        for (int i = 0; i < partCount; i++) {
            long offset = i * PART_SIZE;
            long currentPartSize = Math.min(PART_SIZE, fileSize - offset);
            // 设置分片名称
            String partObjectName = folderPath + "part." + i;
            partNames.add(partObjectName);

            final long partOffset = offset;
            final long partSizeFinal = currentPartSize;
            final String partName = partObjectName;

            // 异步上传每个分片
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                try (InputStream inputStream = file.getInputStream()) { // 获取文件的输入流
                    // 跳过文件前面的部分，直到当前分片的起始位置
                    long skipped = inputStream.skip(partOffset);
                    if (skipped != partOffset) {
                        throw new RuntimeException("Could not skip to the correct part offset.");
                    }

                    byte[] buffer = new byte[(int) partSizeFinal]; // 创建缓冲区来存储分片数据
                    int bytesRead = inputStream.read(buffer); // 读取分片数据
                    if (bytesRead == -1) {
                        throw new RuntimeException("Error reading the part data.");
                    }

                    // 上传分片
                    try (ByteArrayInputStream stream = new ByteArrayInputStream(buffer)) {
                        minioClient.putObject(PutObjectArgs.builder()
                                .bucket(minioConfig.getBucketName())
                                .object(partName)
                                .stream(stream, stream.available(), -1)
                                .build());
                        System.out.println("Uploaded part: " + partName);
                    }
                } catch (Exception e) {
                    throw new RuntimeException("Error uploading part: " + partName, e);
                }
            }, executor); // 指定使用线程池执行任务

            futures.add(future); // 将任务添加到 futures 列表
        }

        // 等待所有分片上传完成
        CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
        allOf.join(); // 阻塞等待所有任务完成

        // 合并所有分片
        List<ComposeSource> sources = new ArrayList<>();
        for (String partName : partNames) {
            sources.add(ComposeSource.builder().bucket(minioConfig.getBucketName()).object(partName).build());
        }

        // 将所有分片合并成最终文件
        try {
            minioClient.composeObject(ComposeObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    // 最终文件的路径
                    .object(folderPath)  // 可以自定义最终文件名
                    .sources(sources)
                    .build());
            System.out.println("Large file uploaded and composed successfully.");
        } catch (MinioException e) {
            throw new Exception("Error during file composition: " + e.getMessage(), e);
        }

        // 删除临时分片
        for (String partName : partNames) {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(minioConfig.getBucketName()).object(partName).build());
            System.out.println("Removed part: " + partName);
        }

        System.out.println("Large file uploaded successfully to folder: " + folderPath);
    }

    /**
     * 下载指定文件，以 InputStream 形式返回

     * @param objectName 存储对象名称（文件名）
     * @return 文件的输入流
     */
    public InputStream downloadFile(String objectName){
        try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
                .bucket(minioConfig.getBucketName())
                .object(objectName)
                .build())) {
            return inputStream;
        } catch (Exception e) {
            LOGGER.error("Error downloading file: {}", e.getMessage(), e);
            return null;
        }

    }


    /**
     * 将多个文件压缩为 ZIP 并上传至 MinIO，返回压缩文件的下载链接
     * @param bucketName 存储桶名称
     * @param objectNames 文件对象列表（文件名）
     * @param zipFileName 压缩后的文件名
     * @return 压缩文件的下载 URL
     * @throws Exception
     */
    public String downloadFilesAsZip(String bucketName, List<String> objectNames, String zipFileName) throws Exception {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ZipOutputStream zos = new ZipOutputStream(baos)) {

            // 压缩每个文件
            for (String objectName : objectNames) {
                try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build())) {

                    // 添加 ZipEntry
                    zos.putNextEntry(new ZipEntry(objectName));
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = inputStream.read(buffer)) > 0) {
                        zos.write(buffer, 0, len);
                    }
                    zos.closeEntry();
                }
            }
            zos.finish();

            // 上传 ZIP 文件到 MinIO
            try (ByteArrayInputStream zipInputStream = new ByteArrayInputStream(baos.toByteArray())) {
                minioClient.putObject(PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(zipFileName)
                        .stream(zipInputStream, zipInputStream.available(), -1)
                        .contentType("application/zip")
                        .build());
            }
        }

        // 返回压缩文件的下载 URL
        return getFileUrl(bucketName, zipFileName);
    }

    /**
     * 获取文件的临时访问 URL，指定过期时间
     * @param bucketName 存储桶名称
     * @param objectName 存储对象名称（文件名）
     * @param expiryTimeInSeconds URL 的有效时长（秒）
     * @return 文件的临时访问 URL
     * @throws Exception 若生成 URL 过程中发生异常
     */
    public String getFileUrl(String bucketName, String objectName, int expiryTimeInSeconds) throws Exception {
        return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .expiry(expiryTimeInSeconds)
                .method(Method.GET)
                .build());
    }

    /**
     * 获取文件的临时访问 URL，默认过期时间为 7 天
     * @param bucketName 存储桶名称
     * @param objectName 存储对象名称（文件名）
     * @return 文件的临时访问 URL
     * @throws Exception 若生成 URL 过程中发生异常
     */
    public String getFileUrl(String bucketName, String objectName) throws Exception {
        // 604800 秒 = 7 天
        return getFileUrl(bucketName, objectName, 604800);
    }


    /**
     * 删除指定的对象
     * @param objectName 存储对象名称（文件名）
     * @throws Exception 若删除过程中发生异常
     */
    public void deleteObject( String objectName) throws Exception {
        minioClient.removeObject(RemoveObjectArgs.builder()
                .bucket(minioConfig.getBucketName())
                .object(objectName)
                .build());
    }

    /**
     * 获取指定文件夹下的所有文件夹和文件。
     *
     * @param folderPath 要获取文件夹和文件的父文件夹路径（例如 "folder1/"）
     * @return 返回一个包含所有文件夹和文件详细信息的集合
     */
    public Set<FileDetails> getFilesAndSubfolders(String folderPath) {
        if (!folderPath.endsWith("/")) {
            folderPath += "/";
        }
        // 存储文件和子文件夹详细信息的集合
        Set<FileDetails> filesAndSubfolders = new HashSet<>();

        try {
            // 列出指定文件夹路径下的所有对象（文件夹和文件）
            Iterable<Result<Item>> objects = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(minioConfig.getBucketName())
                            .prefix(folderPath)  // 设置父文件夹路径前缀
                            .delimiter("/")  // 使用分隔符来模拟获取子文件夹
                            .build()
            );

            // 遍历所有对象，提取文件夹和文件路径
            for (Result<Item> result : objects) {
                Item item = result.get();
                String objectName = item.objectName();

                // 如果是文件夹（路径包含 / 且没有文件名部分），则添加子文件夹
                if (objectName.startsWith(folderPath)) {
                    String subfolder = objectName.substring(folderPath.length());
                    if (subfolder.contains("/")) {
                        // 如果是子文件夹，提取文件夹名称并添加至集合
                        filesAndSubfolders.add(new FileDetails(
                                subfolder.substring(0, subfolder.indexOf("/")),
                                null,
                                0,
                                new Date(),
                                null,
                                null,
                                true
                        ));
                    }
                }
            }

            // 获取文件详细信息
            Iterable<Result<Item>> objectsWithoutDelimiter = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(minioConfig.getBucketName())
                            .prefix(folderPath)
                            .build()
            );

            // 遍历文件对象，获取文件详细信息
            for (Result<Item> result : objectsWithoutDelimiter) {
                Item item = result.get();
                String objectName = item.objectName();

                // 过滤掉文件夹（路径以 '/' 结尾）
                if (!objectName.endsWith("/")) {
                    // 获取文件的详细信息
                    StatObjectResponse stat = minioClient.statObject(
                            StatObjectArgs.builder()
                                    .bucket(minioConfig.getBucketName())
                                    .object(objectName)
                                    .build()
                    );

                    // 封装文件详细信息到 FileDetails 实体类
                    FileDetails fileDetails = new FileDetails(
                            objectName.substring(objectName.lastIndexOf("/") + 1),
                            objectName,
                            stat.size(),
                            Date.from(stat.lastModified().toInstant()),
                            stat.etag(),
                            stat.contentType(),
                            false
                    );

                    filesAndSubfolders.add(fileDetails);
                }
            }

            // 输出文件和子文件夹路径
            System.out.println("Files and subfolders under " + folderPath + ": " + filesAndSubfolders);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return filesAndSubfolders;
    }
    /**
     * 创建文件夹（通过上传带有路径的文件）
     *
     * @param folderPath 文件夹路径（例如 "folder1/subfolder/"）
     */
    public void createFolder(String folderPath) {
        try {
            if (!folderPath.endsWith("/")) {
                folderPath += "/";
            }
            // 创建一个空文件内容，模拟文件夹
            // 空文件内容
            String emptyFileContent = "";
            InputStream emptyFileStream = new ByteArrayInputStream(emptyFileContent.getBytes());

            // 上传一个空文件到指定路径，从而模拟文件夹创建
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(minioConfig.getBucketName())
                            // 使用文件夹路径 + 文件名
                            .object(folderPath + "empty.txt")
                            .stream(emptyFileStream, 0, -1)
                            .contentType("application/text")
                            .build()
            );
        } catch (Exception e) {
            // 使用 LOGGER 打印异常日志
            LOGGER.error("Error occurred while fetching files from bucket: {}",minioConfig.getBucketName(), e);
        }
    }


    /**
     * 删除指定路径的所有对象，从而删除整个文件夹及其子文件夹。
     *
     * @param folderPath 文件夹路径（例如 "folder1/subfolder/"）
     */
    public void deleteFolder(String folderPath) {
        try {
            if (!folderPath.endsWith("/")) {
                folderPath += "/";
            }
            // 列出指定文件夹路径下的所有对象（包括子文件夹中的对象）
            Iterable<Result<Item>> objects = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(minioConfig.getBucketName())
                            .prefix(folderPath) // 设置路径前缀，列出该路径下的所有对象
                            .recursive(true) // 递归列出所有子文件夹中的对象
                            .build()
            );

            // 遍历所有对象并删除它们
            for (Result<Item> result : objects) {
                Item item = result.get();
                String objectName = item.objectName();
                // 删除对象
                minioClient.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(minioConfig.getBucketName())
                                .object(objectName)
                                .build()
                );
                System.out.println("Deleted object: " + objectName);
            }
            System.out.println("Folder and all subfolders deleted successfully at path: " + folderPath);
        } catch (Exception e) {
            System.err.println("Error deleting folder and its contents at path: " + folderPath);
            e.printStackTrace();
        }
    }


}
