package com.ruoyi.system.service.impl;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.ruoyi.common.config.OssFileProperties;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.system.domain.OssFile;
import com.ruoyi.system.domain.OssFileUploadDTO;
import com.ruoyi.system.enums.OssFileEnum;
import com.ruoyi.system.mapper.OssFileMapper;
import com.ruoyi.system.service.OssFileService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * @Author 吴凯程
 * @Date 2025/6/16
 **/
@Service
@Slf4j
public class OssFileServiceImpl implements OssFileService {

    @Resource
    private OssFileProperties ossFileProperties;

    @Resource
    private OssFileMapper ossFileMapper;

    @SneakyThrows
    @Override
    /**
     * 将文件上传至OSS（阿里云对象存储服务）
     * 该方法处理文件上传的准备工作，包括生成文件的元数据，并实际上传文件至OSS
     *
     * @param ossFileUploadDTO 包含上传文件所需信息的数据传输对象，包括文件本身和文件分类枚举
     */
    public String uploadFileToOss(OssFileUploadDTO ossFileUploadDTO) {
        // 创建一个新的OssFile对象，用于存储文件元数据
        OssFile ossFile = new OssFile();

        // 生成文件的唯一标识符，用于文件在OSS中的命名
        String uuid = IdUtils.fastSimpleUUID();

        // 获取上传文件的原始文件名
        String originalFileName = ossFileUploadDTO.getFile().getOriginalFilename();

        // 构造在OSS中存储的文件名，格式为：UUID.原始文件扩展名
        String fileName = uuid + originalFileName.substring(originalFileName.lastIndexOf("."));

        // 设置文件的Content-Type，基于原始文件名的扩展名
        ossFile.setContentType(ossFileUploadDTO.getFile().getOriginalFilename().substring(ossFileUploadDTO.getFile().getOriginalFilename().lastIndexOf(".") + 1));

        // 设置文件的唯一标识符
        ossFile.setFileId(uuid);

        // 设置在OSS中存储的文件名
        ossFile.setFileName(fileName);

        // 设置文件的原始名称
        ossFile.setOriginalFileName(originalFileName);

        // 设置文件的分类枚举
        ossFile.setFileEnum(ossFileUploadDTO.getOssFileEnum());

        ossFile.setUserName(ossFileUploadDTO.getUserName());

        // 设置文件大小，单位为KB
        ossFile.setFileSize( ossFileUploadDTO.getFile().getSize() / 1024.0);

        // 上传文件至OSS，并获取文件的URL
        String url = upload(ossFileUploadDTO.getOssFileEnum(), fileName, ossFileUploadDTO.getFile());

        // 设置文件的URL
        ossFile.setFileUrl(url);

        ossFile.setCreateTime(new Date());

        // 插入文件元数据至数据库
        if( ossFileMapper.insert(ossFile) > 0){
            log.info("文件上传至OSS, originalFileName = " + originalFileName);
            return uuid;
        }
        return null;
    }

    @SneakyThrows
    @Override
    /**
     * 将文件上传至OSS（阿里云对象存储服务）
     * 该方法处理文件上传的准备工作，包括生成文件的元数据，并实际上传文件至OSS
     *
     * @param ossFileUploadDTO 包含上传文件所需信息的数据传输对象，包括文件本身和文件分类枚举
     */
    public String uploadFileToOss(OssFileEnum ossFileEnum, File file, String originalFileName, String userName) {
        // 创建一个新的OssFile对象，用于存储文件元数据
        OssFile ossFile = new OssFile();

        // 生成文件的唯一标识符，用于文件在OSS中的命名
        String uuid = IdUtils.fastSimpleUUID();

        // 构造在OSS中存储的文件名，格式为：UUID.原始文件扩展名
        String fileName = uuid + originalFileName.substring(originalFileName.lastIndexOf("."));

        // 设置文件的Content-Type，基于原始文件名的扩展名
        ossFile.setContentType(originalFileName.substring(originalFileName.lastIndexOf(".") + 1));

        // 设置文件的唯一标识符
        ossFile.setFileId(uuid);

        // 设置在OSS中存储的文件名
        ossFile.setFileName(fileName);

        // 设置文件的原始名称
        ossFile.setOriginalFileName(originalFileName);

        // 设置文件的分类枚举
        ossFile.setFileEnum(ossFileEnum);

        ossFile.setUserName(userName);

        // 设置文件大小，单位为KB
        ossFile.setFileSize( file.length() / 1024.0);

        // 上传文件至OSS，并获取文件的URL
        String url = upload(ossFileEnum, fileName, file);

        // 设置文件的URL
        ossFile.setFileUrl(url);

        ossFile.setCreateTime(new Date());

        // 插入文件元数据至数据库
        if( ossFileMapper.insert(ossFile) > 0){
            log.info("文件上传至OSS, originalFileName = " + originalFileName);
            return uuid;
        }
        return null;
    }

    /**
     * @param uuid
     * @return
     */
    @Override
    public MultipartFile getFileFromOss(String uuid) {
        OssFile ossFile = ossFileMapper.selectById(uuid);
        if (Objects.isNull(ossFile)){
            throw new ServiceException("文件不存在");
        }
        return getMultipartFile(ossFile.getFileEnum(), ossFile.getFileName(), ossFile.getOriginalFileName());
    }

    /**
     * @param uuid
     * @return
     */
    @Override
    public void deleteFileFromOss(String uuid) {
        OssFile ossFile = ossFileMapper.selectById(uuid);
        if (Objects.isNull(ossFile)){
            throw new ServiceException("文件不存在");
        }
        OSS ossClient = new OSSClientBuilder().build(ossFileProperties.getEndpoint(), ossFileProperties.getAccessKeyId(), ossFileProperties.getAccessKeySecret());
        try {
            ossClient.deleteObject(ossFileProperties.getBucketName(), ossFile.getFileEnum().getPrefix() + ossFile.getFileName());
            log.info("文件刪除 ,文件名 = " + ossFile.getFileEnum().getPrefix() + ossFile.getFileName());
            if (!ossClient.doesObjectExist(ossFileProperties.getBucketName(), ossFile.getFileEnum().getPrefix() + ossFile.getFileName())){
                ossFileMapper.deleteById(uuid);
            }
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * @param uuids
     */
    @Override
    public void batchDeleteFileFromOss(List<String> uuids) {
        List<OssFile> ossFiles = ossFileMapper.selectBatchIds(uuids);
        OSS ossClient = new OSSClientBuilder().build(ossFileProperties.getEndpoint(), ossFileProperties.getAccessKeyId(), ossFileProperties.getAccessKeySecret());
        try {
            for (OssFile ossFile : ossFiles) {
                if (Objects.isNull(ossFile)){
                    log.error("文件不存在: " + ossFile.getFileEnum().getPrefix() + ossFile.getFileName());
                    ossFileMapper.deleteById(ossFile.getFileId());
                    continue;
                }
                ossClient.deleteObject(ossFileProperties.getBucketName(), ossFile.getFileEnum().getPrefix() + ossFile.getFileName());
                if (!ossClient.doesObjectExist(ossFileProperties.getBucketName(), ossFile.getFileEnum().getPrefix() + ossFile.getFileName())){
                    ossFileMapper.deleteById(ossFile.getFileId());
                }
                log.info("文件刪除 ,文件名 = " + ossFile.getFileEnum().getPrefix() + ossFile.getFileName());
            }
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }


    @SneakyThrows
    private String upload(OssFileEnum ossFileEnum, String fileName, MultipartFile file){
        OSS ossClient = new OSSClientBuilder().build(ossFileProperties.getEndpoint(), ossFileProperties.getAccessKeyId(), ossFileProperties.getAccessKeySecret());
        String url = new String();
        Date no = new Date();
        no.setYear(no.getYear() + 5);
        try (InputStream inputStream = file.getInputStream()) {
            ossClient.putObject(ossFileProperties.getBucketName(), ossFileEnum.getPrefix() + fileName, inputStream);
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(ossFileProperties.getBucketName(), fileName);
            request.setExpiration(no);
            url = ossClient.generatePresignedUrl(request).toString();
            log.info("文件上传 ,url = " + url);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return url;
    }

    @SneakyThrows
    private String upload(OssFileEnum ossFileEnum, String fileName, File file){
        OSS ossClient = new OSSClientBuilder().build(ossFileProperties.getEndpoint(), ossFileProperties.getAccessKeyId(), ossFileProperties.getAccessKeySecret());
        String url = new String();
        Date no = new Date();
        no.setYear(no.getYear() + 5);
        try (InputStream inputStream = new FileInputStream(file)) {
            ossClient.putObject(ossFileProperties.getBucketName(), ossFileEnum.getPrefix() + fileName, inputStream);
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(ossFileProperties.getBucketName(), fileName);
            request.setExpiration(no);
            url = ossClient.generatePresignedUrl(request).toString();
            log.info("文件上传 ,url = " + url);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return url;
    }

    @SneakyThrows
    private InputStream getInputStreamByName(OssFileEnum ossFileEnum, String fileName) {
        OSS ossClient = new OSSClientBuilder().build(ossFileProperties.getEndpoint(), ossFileProperties.getAccessKeyId(), ossFileProperties.getAccessKeySecret());
        try {
            return IOUtils.toBufferedInputStream(ossClient.getObject(ossFileProperties.getBucketName(), ossFileEnum.getPrefix() + fileName).getObjectContent());
        } finally {
            ossClient.shutdown();
        }
    }

    @SneakyThrows
    // 创建一个MultipartFile对象，用于在测试中模拟文件上传
    // 参数:
    // - fileName: 文件名，用于从类路径中获取文件输入流
    // - originalFileName: 原始文件名，用于设置MultipartFile的名称
    // 返回: 一个包含指定文件内容的MultipartFile对象
    private MultipartFile getMultipartFile(OssFileEnum ossFileEnum, String fileName, String originalFileName) {
        // 创建并返回一个MockMultipartFile对象，它实现了MultipartFile接口
        // 这里使用原始文件名和通过文件名获取的输入流来初始化MockMultipartFile
        return new MockMultipartFile(originalFileName, getInputStreamByName(ossFileEnum, fileName));
    }
}
