package com.ruoyi.platform.utils;


import io.minio.*;
import io.minio.errors.MinioException;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
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 java.io.*;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
@Component
public class MinioUtil {

    private static MinioClient minioClient;

    @Value("${git.localPath}")
    String localPath;
    @Value("${minio.endpointIp}")
    String minioEndpoint;

    @Autowired
    public MinioUtil(@Value("${minio.endpoint}") String minioEndpoint, @Value("${minio.accessKey}") String minioAccessKey, @Value("${minio.secretKey}") String minioSecretKey) {
        this.minioClient = MinioClient.builder()
                .endpoint(minioEndpoint)
                .credentials(minioAccessKey, minioSecretKey)
                .build();
    }

    public void createBucket(String bucketName) throws Exception {
        if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    public void removeBucket(String bucketName) throws Exception {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 验证bucketName是否存在
     *
     * @return boolean true:存在
     */
    public boolean bucketExists(String bucketName)
            throws Exception {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 判断文件是否存在
     *
     * @param bucketName 存储桶
     * @param objectName 对象
     * @return true：存在
     */
    public boolean doesObjectExist(String bucketName, String objectName) {
        boolean exist = true;
        try {
            minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            exist = false;
        }
        return exist;
    }

    /**
     * 判断文件夹是否存在
     *
     * @param bucketName 存储桶
     * @param objectName 文件夹名称（去掉/）
     * @return true：存在
     */
    public boolean doesFolderExist(String bucketName, String objectName) {
        boolean exist = false;
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
            for (Result<Item> result : results) {
                Item item = result.get();
                if (item.isDir() && objectName.equals(item.objectName())) {
                    exist = true;
                }
            }
        } catch (Exception e) {
            exist = false;
        }
        return exist;
    }

    /**
     * 根据文件前置查询文件
     *
     * @param bucketName bucket名称
     * @param prefix     前缀
     * @param recursive  是否递归查询
     * @return MinioItem 列表
     */
    public List<Item> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) throws Exception {
        List<Item> list = new ArrayList<>();
        Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
                ListObjectsArgs.
                        builder().
                        bucket(bucketName).
                        prefix(prefix).
                        recursive(recursive).
                        build());
        if (objectsIterator != null) {
            for (Result<Item> o : objectsIterator) {
                Item item = o.get();
                list.add(item);
            }
        }
        return list;
    }


    public void uploadObject(String bucketName, String objectName, InputStream stream) throws Exception {
        long size = stream.available();
        minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .stream(stream, size, -1)
                        .build()
        );
    }

    /**
     * 通过MultipartFile，上传文件
     *
     * @param bucketName  存储桶
     * @param file        文件
     * @param objectName  对象名
     * @param contentType 文件类型
     */
    public static ObjectWriteResponse putObject(String bucketName, MultipartFile file, String objectName, String contentType) throws Exception {
        InputStream inputStream = file.getInputStream();
        return minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType)
                        .stream(inputStream, inputStream.available(), -1).build());
    }

    /**
     * 通过MultipartFile，上传文件
     *
     * @param bucketName 存储桶
     * @param file       文件
     * @param objectName 对象名
     */
    public static ObjectWriteResponse putObject(String bucketName, MultipartFile file, String objectName) throws Exception {
        InputStream inputStream = file.getInputStream();
        return minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName)
                        .stream(inputStream, inputStream.available(), -1).build());
    }


    public void downloadObject(String bucketName, String objectName, OutputStream stream) throws Exception {
        try (InputStream inStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build())) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = inStream.read(buffer)) != -1) {
                stream.write(buffer, 0, bytesRead);
            }
        }
    }

    /**
     * 创建文件夹或目录
     *
     * @param bucketName 存储桶
     * @param objectName 目录路径
     */
    public ObjectWriteResponse putDirObject(String bucketName, String objectName) throws Exception {
        return minioClient.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
                        new ByteArrayInputStream(new byte[]{}), 0, -1).build());
    }

    /**
     * 拷贝文件
     *
     * @param bucketName    bucket名称
     * @param objectName    文件名称
     * @param srcBucketName 目标bucket名称
     * @param srcObjectName 目标文件名称
     */
    public ObjectWriteResponse copyObject(String bucketName, String objectName,
                                          String srcBucketName, String srcObjectName) throws Exception {
        return minioClient.copyObject(
                CopyObjectArgs.builder()
                        .source(CopySource.builder().bucket(bucketName).object(objectName).build())
                        .bucket(srcBucketName)
                        .object(srcObjectName)
                        .build());
    }


    /**
     * 递归拷贝
     *
     * @param sourceBucketName 源bucket名称
     * @param sourceKeyPrefix  源目录路径
     * @param targetBucketName 目标bucket名称
     * @param targetKeyPrefix  目标目录名称
     */
    public void copyDirectory(String sourceBucketName, String sourceKeyPrefix,
                              String targetBucketName, String targetKeyPrefix) throws Exception {
        // 列出所有源目录下的对象
        Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                .bucket(sourceBucketName)
                .prefix(sourceKeyPrefix)
                .recursive(true)
                .build());

        for (Result<Item> result : results) {
            Item item = result.get();
            String sourceKey = item.objectName(); //文件的原始完整路径
            String targetKey = targetKeyPrefix + sourceKey.substring(sourceKeyPrefix.length());

            // 拷贝每个对象到目标路径
            minioClient.copyObject(CopyObjectArgs.builder()
                    .bucket(targetBucketName)
                    .object(targetKey)
                    .source(CopySource.builder()
                            .bucket(sourceBucketName)
                            .object(sourceKey)
                            .build())
                    .build());
        }
    }


    /**
     * 获取文件流
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @return 二进制流
     */
    public static InputStream getObject(String bucketName, String objectName) throws Exception {
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 断点下载
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @param offset     起始字节的位置
     * @param length     要读取的长度
     * @return 流
     */
    public InputStream getObject(String bucketName, String objectName, long offset, long length)
            throws Exception {
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build());
    }


    public void downloadFiles(String bucketName, String root_path, String targetPath) throws Exception {
        ListObjectsArgs listObjectsArgs = ListObjectsArgs.builder().bucket(bucketName).prefix(root_path).build();
        findAndSaveFile(listObjectsArgs, bucketName, targetPath);
    }

    private void findAndSaveFile(ListObjectsArgs listObjectsArgs, String bucketName, String targetPath) throws Exception {
        // List all objects in the bucket
        Iterable<Result<Item>> results = minioClient.listObjects(listObjectsArgs);
        for (Result<Item> result : results) {
            Item item = result.get();
            if (item.isDir()) {
                System.out.println("文件夹：" + URLDecoder.decode(item.objectName(), "UTF-8"));//输出日志
                ListObjectsArgs args = ListObjectsArgs.builder().bucket(bucketName).prefix(item.objectName()).build();
                findAndSaveFile(args, bucketName, targetPath);
            } else {
                GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucketName).object(URLDecoder.decode(item.objectName())).build();
                String objectName = URLDecoder.decode(item.objectName(), "UTF-8");
                // Create a local file with the same name as the object
                Path fileName = Paths.get(objectName).getFileName();
                String targetFile = targetPath + "/" + fileName.toString();
                File file = new File(targetFile);
                // Create parent directories if needed
                file.getParentFile().mkdirs();
                // Get the object as an input stream
                try (InputStream stream = minioClient.getObject(getObjectArgs)) {
                    // Copy the input stream to the file
                    FileUtils.copyInputStreamToFile(stream, file);
                }
                System.out.printf("文件：%s 下载成功！\n", URLDecoder.decode(item.objectName()));//输出日志
            }
        }
    }

    public List<Map> listFilesInDirectory(String bucketName, String prefix) throws Exception {
        List<Map> fileInfoList = new ArrayList<>();
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder()
                        .prefix(prefix)
                        .bucket(bucketName)
                        .build());

        for (Result<Item> result : results) {
            Item item = result.get();
            String fullPath = item.objectName();
            Path path = Paths.get(fullPath);

            String fileName = path.getFileName().toString();
            long fileSize = item.size();
            String formattedSize = FileUtil.formatFileSize(fileSize); // 格式化文件大小
            Map map = new HashMap<>();
            map.put("name", fileName);
            map.put("size", formattedSize);
            fileInfoList.add(map);
        }

        return fileInfoList;
    }


    public List<Map> listRayFilesInDirectory(String bucketName, String prefix) throws Exception {
        List<Map> fileInfoList = new ArrayList<>();
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder()
                        .prefix(prefix)
                        .bucket(bucketName)
                        .build());

        for (Result<Item> result : results) {
            Item item = result.get();
            String fullPath = item.objectName();
            Path path = Paths.get(fullPath);

            String fileName = path.getFileName().toString();
            long fileSize = item.size();
            String formattedSize = FileUtil.formatFileSize(fileSize); // 格式化文件大小
            Map map = new HashMap<>();
            map.put("name", fileName);
            map.put("size", formattedSize);

            if ((fileName.startsWith("run") || fileName.startsWith("checkpoint") || fileName.equals("log")) && fileSize == 0) {
                map.put("isDirectory", true);
                map.put("children", listRayFilesInDirectory(bucketName, fullPath));
            } else {
                map.put("isFile", true);
            }
            map.put("url", bucketName + "/" + fullPath);
            fileInfoList.add(map);
        }
        return fileInfoList;
    }

    public List<Map> listALFilesInDirectory(String bucketName, String prefix) throws Exception {
        List<Map> fileInfoList = new ArrayList<>();
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder()
                        .prefix(prefix)
                        .bucket(bucketName)
                        .build());

        for (Result<Item> result : results) {
            Item item = result.get();
            String fullPath = item.objectName();
            Path path = Paths.get(fullPath);

            String fileName = path.getFileName().toString();
            long fileSize = item.size();
            String formattedSize = FileUtil.formatFileSize(fileSize); // 格式化文件大小
            Map map = new HashMap<>();
            map.put("name", fileName);
            map.put("size", formattedSize);

            if (fileName.endsWith("checkpoint") && fileSize == 0) {
                map.put("isDirectory", true);
                map.put("children", listALFilesInDirectory(bucketName, fullPath));
            } else {
                map.put("isFile", true);
            }
            map.put("url", bucketName + "/" + fullPath);
            fileInfoList.add(map);
        }
        return fileInfoList;
    }

    /**
     * 删除文件
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     */
    public static void removeObject(String bucketName, String objectName) throws Exception {
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }


    /**
     * 批量删除文件
     *
     * @param bucketName bucket
     * @param keys       需要删除的文件列表
     */
    public static void removeObjects(String bucketName, List<String> keys) {
        List<DeleteObject> objects = new LinkedList<>();
        keys.forEach(s -> {
            objects.add(new DeleteObject(s));
            try {
                removeObject(bucketName, s);
            } catch (Exception e) {
                System.err.println("批量删除失败!");
            }
        });
    }


    public String readObjectAsString(String bucketName, String objectName) throws Exception {
        try (InputStream inputStream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build())) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            StringBuilder content = new StringBuilder();

            while ((bytesRead = inputStream.read(buffer)) != -1) {
                content.append(new String(buffer, 0, bytesRead, StandardCharsets.UTF_8));
            }

            return content.toString();
        }
    }

    /**
     * Downloads files and folders from the specified bucket and path, and creates a zip archive.
     *
     * @param bucketName The name of the bucket.
     * @param path       The path within the bucket.
     * @return InputStream containing the zip archive.
     * @throws MinioException If an error occurs while communicating with Minio.
     * @throws IOException    If an I/O error occurs during zip creation.
     */
    public InputStream downloadAndZip(String bucketName, String path) throws Exception {
        try (ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();
             ZipOutputStream zip = new ZipOutputStream(zipOutputStream)) {

            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(path).recursive(true).build());

            for (Result<Item> result : results) {
                Item item = result.get();
                String objectName = item.objectName();
                if (!objectName.contains(".git")) {
                    InputStream objectStream = minioClient.getObject(
                            GetObjectArgs.builder().bucket(bucketName).object(objectName).build());


                    // Create a zip entry for each object
                    ZipEntry zipEntry = new ZipEntry(objectName);
                    zip.putNextEntry(zipEntry);

                    // Write object data to zip stream
                    byte[] buffer = new byte[1024];
                    int bytesRead;
                    while ((bytesRead = objectStream.read(buffer)) != -1) {
                        zip.write(buffer, 0, bytesRead);
                    }
                    zip.closeEntry();
                    objectStream.close();
                }
            }

            zip.finish();
            return new ByteArrayInputStream(zipOutputStream.toByteArray());
        }
    }
}