package com.bonus.file.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.bonus.common.core.domain.R;
import com.bonus.common.core.utils.file.FileUtils;
import com.bonus.file.config.OSSConfig;
import com.bonus.file.entity.FileDetails;
import com.bonus.system.api.domain.SysFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * OSS存储服务类
 * @author jiang
 */
@Service
@ConditionalOnProperty(name = "storage.type", havingValue = "oss")
public class OssUtils {

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

    @Resource
    private OSSConfig ossConfig;

    /**
     * OSS客户端
     */
    private OSS ossClient;

    /**
     * 初始化OSS客户端
     */
    @PostConstruct
    public void init() {
        ossClient = new OSSClientBuilder().build(ossConfig.getEndpoint(), ossConfig.getAccessKeyId(), ossConfig.getAccessKeySecret());
    }

    /**
     * 上传文件到OSS
     *
     * @param objectKey 文件在OSS中的键
     * @param file      要上传的文件
     * @return 包含上传文件信息的结果对象
     */
    public SysFile upload(String objectKey, File file) throws Exception {
        try {
            if (file.length() < 10 * 1024 * 1024L) {
                ossClient.putObject(ossConfig.getBucket(), objectKey, file);
            } else {
                ossMultipartParallelUpload(objectKey, file);
            }

            return getInfo(objectKey);
        } catch (Exception e) {
            LOGGER.error("文件上传失败", e);
            return null;
        } finally {
            if (!file.delete()) {
                LOGGER.warn("临时文件删除失败");
            }
        }
    }

    /**
     * 并行方式实现OSS的大文件分片上传。
     * 通过多线程并行上传文件的各个分片，提高上传效率。
     *
     * @param objectKey OSS对象的关键字，即文件名。
     * @param file      需要上传到OSS的本地文件。
     */
    public void ossMultipartParallelUpload(String objectKey, File file) {
        // 定义线程池大小，用于并发上传分片
        // 调整线程池大小
        int threadPoolSize = 10;
        ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
        try {
            // 初始化分片上传，获取上传ID
            // 1. 初始化分段上传
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(ossConfig.getBucket(), objectKey);
            InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
            String uploadId = result.getUploadId();

            // 计算分片大小和分片数量
            // 2. 上传分段
            // 调整分段大小，例如10MB
            final long partSize = 10 * 1024 * 1024L;
            long fileLength = file.length();
            int partCount = (int) (fileLength / partSize);
            if (fileLength % partSize != 0) {
                partCount++;
            }

            // 使用Future列表保存每个分片上传的结果
            List<Future<PartETag>> futures = new ArrayList<>();
            for (int i = 0; i < partCount; i++) {
                final int partNumber = i + 1;
                final long startPos = i * partSize;
                final long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
                futures.add(executorService.submit(() -> {
                    try (InputStream instream = new FileInputStream(file)) {
                        instream.skip(startPos);
                        UploadPartRequest uploadPartRequest = new UploadPartRequest();
                        uploadPartRequest.setBucketName(ossConfig.getBucket());
                        uploadPartRequest.setKey(objectKey);
                        uploadPartRequest.setUploadId(uploadId);
                        uploadPartRequest.setInputStream(instream);
                        uploadPartRequest.setPartSize(curPartSize);
                        uploadPartRequest.setPartNumber(partNumber);
                        UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
                        return uploadPartResult.getPartETag();
                    }
                }));
            }

            // 收集所有分片的上传结果
            List<PartETag> partETags = new ArrayList<>();
            for (Future<PartETag> future : futures) {
                partETags.add(future.get());
            }

            // 完成分片上传
            // 4. 完成分段上传
            CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(ossConfig.getBucket(), objectKey, uploadId, partETags);
            ossClient.completeMultipartUpload(completeMultipartUploadRequest);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池
            executorService.shutdown();
        }
    }


    /**
     * 获取文件在OSS中的信息
     *
     * @param objectKey 文件路径
     * @return 文件信息对象
     */
    public SysFile getInfo(String objectKey) {
        try {
            ObjectMetadata objectMetadata = ossClient.getObjectMetadata(ossConfig.getBucket(), objectKey);
            return SysFile.builder()
                    .name(FileUtils.getName(objectKey))
                    .url(objectKey).build();
        } catch (Exception e) {
            LOGGER.error("获取文件信息失败", e);
            return SysFile.builder().build();
        }
    }

    /**
     * 从OSS下载文件
     *
     * @param objectKey 文件路径
     * @return OSSObject对象，包含下载的文件
     */
    public R<OSSObject> download(String objectKey) throws Exception {
        if (doesObjectExist(ossConfig.getBucket(), objectKey)) {
            throw new Exception("文件不存在");
        }
        try {
            return R.ok(ossClient.getObject(ossConfig.getBucket(), objectKey));
        } catch (Exception e) {
            LOGGER.error("文件下载失败", e);
            throw new Exception("文件下载失败");
        }
    }

    /**
     * 删除OSS中的文件
     *
     * @param objectKey 文件路径
     * @return 删除操作的结果
     */
    public boolean delete(String objectKey) throws Exception {
        try {
            if (doesObjectExist(ossConfig.getBucket(), objectKey)) {
                throw new Exception("删除文件时文件不存在");
            }
            ossClient.deleteObject(ossConfig.getBucket(), objectKey);
            return true;
        } catch (Exception e) {
            LOGGER.error("文件删除失败", e);
            throw new Exception("删除文件时文件删除失败");
        }
    }

    /**
     * 检查OSS中是否存在指定的文件
     *
     * @param bucketName Bucket名称
     * @param objectKey  文件路径
     * @return 如果文件存在返回true，否则返回false
     */
    private boolean doesObjectExist(String bucketName, String objectKey) throws Exception {
        try {
            return !ossClient.doesObjectExist(bucketName, objectKey);
        } catch (Exception e) {
            throw new Exception(e.getMessage());
        }
    }


    /**
     * 获取指定文件夹下所有文件夹和文件的详细信息。
     *
     * @param folderPath 要获取文件夹和文件的父文件夹路径（例如 "folder1/"）
     * @return 返回包含所有文件夹和文件详细信息的集合
     */
    public Set<FileDetails> getFilesAndFolders(String folderPath) {
        Set<FileDetails> filesAndFolders = new HashSet<>();
        try {
            ListObjectsRequest request = new ListObjectsRequest(ossConfig.getBucket());
            request.setPrefix(folderPath);
            request.setDelimiter("/");

            ObjectListing objectListing;
            do {
                objectListing = ossClient.listObjects(request);

                // 处理文件夹
                for (String commonPrefix : objectListing.getCommonPrefixes()) {
                    filesAndFolders.add(new FileDetails(
                            commonPrefix,
                            null,
                            0,
                            null,
                            null,
                            null,
                            true // isFolder
                    ));
                }

                // 处理文件
                for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
                    filesAndFolders.add(new FileDetails(
                            objectSummary.getKey().substring(objectSummary.getKey().lastIndexOf("/") + 1),
                            objectSummary.getKey(),
                            objectSummary.getSize(),
                            objectSummary.getLastModified(),
                            objectSummary.getETag(),
                            objectSummary.getType(),
                            false // isFolder
                    ));
                }

                request.setMarker(objectListing.getNextMarker());
            } while (objectListing.isTruncated());

        } catch (Exception e) {
            System.err.println("Error listing files and folders at path: " + folderPath);
            e.printStackTrace();
        }
        return filesAndFolders;
    }


    /**
     * 在 OSS 中创建文件夹。
     *
     * @param folderPath 文件夹路径（例如 "folder1/subfolder/"）
     */
    public void createFolder(String folderPath) {
        try {
            // 设置文件夹的元数据
            ObjectMetadata metadata = new ObjectMetadata();
            // 文件内容长度设为 0，表示空文件
            metadata.setContentLength(0);

            // 通过空文件创建文件夹
            ossClient.putObject(ossConfig.getBucket(), folderPath, new ByteArrayInputStream(new byte[0]), metadata);
            System.out.println("Folder created successfully at path: " + folderPath);
        } catch (Exception e) {
            System.err.println("Error creating folder at path: " + folderPath);
            e.printStackTrace();
        }
    }

    /**
     * 删除指定路径的所有对象，从而删除整个文件夹及其子文件夹。
     *
     * @param folderPath 文件夹路径（例如 "folder1/subfolder/"）
     */
    public void deleteFolder( String folderPath) {
        try {
            String nextMarker = null;
            ObjectListing objectListing;

            do {
                // 列出指定路径下的所有对象
                objectListing = ossClient.listObjects(ossConfig.getBucket(), folderPath);
                List<String> keysToDelete = new ArrayList<>();

                // 获取对象的键并添加到删除列表
                for (OSSObjectSummary summary : objectListing.getObjectSummaries()) {
                    keysToDelete.add(summary.getKey());
                }

                // 删除所有列出的对象
                if (!keysToDelete.isEmpty()) {
                    DeleteObjectsRequest deleteRequest = new DeleteObjectsRequest(ossConfig.getBucket()).withKeys(keysToDelete);
                    ossClient.deleteObjects(deleteRequest);
                    System.out.println("Deleted objects: " + keysToDelete);
                }

                // 获取下一页标记
                nextMarker = objectListing.getNextMarker();
            } while (objectListing.isTruncated()); // 如果有更多对象，则继续

            System.out.println("Folder and all subfolders deleted successfully at path: " + folderPath);

        } catch (Exception e) {
            System.err.println("Error deleting folder at path: " + folderPath);
            e.printStackTrace();
        }
    }
}
