package com.sonic.shark.utils;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.google.common.collect.Lists;
import com.sonic.shark.enums.ContentTypeEnum;
import com.sonic.shark.enums.S3BucketNameEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 亚马逊存储 文件存储系统使用的工具类型
 *
 * @Author code
 */
@Component
@Slf4j
public class AwsS3Utils {

    @Value("${aws.s3.fileOpt.accessKeyId}")
    private String accessKeyId;
    @Value("${aws.s3.fileOpt.secretAccessKey}")
    private String secretAccessKey;

    /**
     * S3连接对象缓存Map
     */
    private static Map<S3BucketNameEnum, S3Client> s3ClientMap = new ConcurrentHashMap<>();

    /**
     * 根据bucket枚举类型获取连接对象
     *
     * @param s3BucketNameEnum
     * @return
     */
    public S3Client getS3client(S3BucketNameEnum s3BucketNameEnum) {
        S3Client s3Client = s3ClientMap.get(s3BucketNameEnum);
        if (s3Client != null) {
            return s3Client;
        }
        AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKeyId, secretAccessKey);
        s3Client = S3Client.builder()
                .region(s3BucketNameEnum.getRegion())
                .credentialsProvider(StaticCredentialsProvider.create(credentials)).build();
        s3ClientMap.put(s3BucketNameEnum, s3Client);
        return s3Client;
    }


    /**
     * 上传原始文件名的文件到S3
     *
     * @param file
     * @param filePath   注意filePath结尾需要由/ (eg: sound/temp/ )
     * @param publicRead
     * @return
     * @throws IOException
     */
    public String uploadOriginalFileNameFile(MultipartFile file, S3BucketNameEnum s3BucketNameEnum, String filePath, boolean publicRead) throws IOException {
        return uploadFile(file.getBytes(), s3BucketNameEnum, filePath, file.getOriginalFilename(), publicRead);
    }

    /**
     * 上传随机文件名的文件到S3
     *
     * @param file
     * @param filePath   注意filePath结尾需要由/ (eg: sound/temp/ )
     * @param publicRead
     * @return
     * @throws IOException
     */
    public String uploadRandomFileNameFile(MultipartFile file, S3BucketNameEnum s3BucketNameEnum, String filePath, boolean publicRead) throws IOException {
        Random random = new Random();
        //文件前缀名生成规则：当前时间戳 + 随机数 后得到一个数值 作为文件名
        String filePrefix = System.currentTimeMillis() + random.nextInt(10000) + "";
        //获取文件名称的后缀
        String fileSuffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")).toLowerCase();
        //将 文件前缀 + 文件后缀 拼接成文件名全称（eg:1631863654478.png）
        String fileName = filePrefix + fileSuffix;
        return uploadFile(file.getBytes(), s3BucketNameEnum, filePath, fileName, publicRead);
    }

    /**
     * 上传随机文件名的文件到S3
     *
     * @param bytes
     * @param filePath   注意filePath结尾需要由/ (eg: sound/temp/ )
     * @param publicRead
     * @return
     * @throws IOException
     */
    public String uploadRandomFileNameFile(byte[] bytes, S3BucketNameEnum s3BucketNameEnum, String filePath, boolean publicRead) throws IOException {
        Random random = new Random();
        String fileName = System.currentTimeMillis() + "" + random.nextInt(10000) + "." + "jpg";
        return uploadFile(bytes, s3BucketNameEnum, filePath, fileName, publicRead);
    }

    /**
     * 上传文件到S3的基础工具方法
     *
     * @param bytes
     * @param s3BucketNameEnum
     * @param filePath         注意filePath结尾需要由/ (eg: sound/temp/ )
     * @param fileName
     * @param publicRead
     * @return
     */
    public String uploadFile(byte[] bytes, S3BucketNameEnum s3BucketNameEnum, String filePath, String fileName, boolean publicRead) {
        return uploadFile(bytes, null, s3BucketNameEnum, filePath, fileName, publicRead);
    }

    /**
     * 上传文件到S3的基础工具方法
     *
     * @param bytes
     * @param tagList
     * @param s3BucketNameEnum
     * @param filePath         注意filePath结尾需要由/ (eg: sound/temp/ )
     * @param fileName
     * @param publicRead
     * @return
     */
    public String uploadFile(byte[] bytes, List<Tag> tagList, S3BucketNameEnum s3BucketNameEnum, String filePath, String fileName, boolean publicRead) {
        try {
            String uploadDir = filePath + fileName;
            log.info("===> AwsS3Utils upload param , bucketName : {} , uploadDir : {} , publicRead : {}", s3BucketNameEnum.getBucketName(), uploadDir, publicRead);
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .bucket(s3BucketNameEnum.getBucketName())
                    .key(uploadDir)
//                    .acl(publicRead ? CannedAccessControlList.PublicRead.toString() : CannedAccessControlList.Private.toString())
                    .contentType(getContentType(fileName.substring(fileName.lastIndexOf("."))))
                    .cacheControl("no-cache")
                    .build();
            if (CollectionUtils.isNotEmpty(tagList)) {
                putObjectRequest = putObjectRequest.toBuilder().build().toBuilder().tagging(Tagging.builder().tagSet(tagList).build()).build();
            }

            PutObjectResponse response = getS3client(s3BucketNameEnum).putObject(putObjectRequest, RequestBody.fromBytes(bytes));
            log.info("===> AwsS3Utils upload response : {}", response);
            return s3BucketNameEnum.getRootUrl() + uploadDir;
        } catch (Exception e) {
            log.error("===> AwsS3Utils upload error : ", e);
        }
        return null;
    }

    /**
     * 删除文件对应的所有标签
     *
     * @param s3BucketNameEnum
     * @param fileFullPath
     * @return
     */
    public Boolean delObjectAllTag(S3BucketNameEnum s3BucketNameEnum, String fileFullPath) {
        try {
            log.info("===> AwsS3Utils delObjectAllTag param , bucketName : {} , fileFullPath : {}", s3BucketNameEnum.getBucketName(), fileFullPath);
            DeleteObjectTaggingRequest request = DeleteObjectTaggingRequest.builder()
                    .bucket(s3BucketNameEnum.getBucketName())
                    .key(fileFullPath)
                    .build();
            DeleteObjectTaggingResponse response = getS3client(s3BucketNameEnum).deleteObjectTagging(request);
            log.info("===> AwsS3Utils delObjectAllTag response : {}", response);
        } catch (Exception e) {
            log.error("===> AwsS3Utils delObjectAllTag error : ", e);
            return false;
        }
        return true;
    }

    /**
     * 删除文件的指定tag标签
     *
     * @param s3BucketNameEnum
     * @param fileFullPath
     * @param tagKey
     * @return 返回object对象的原始tag对象
     */
    public List<Tag> delObjectTag(S3BucketNameEnum s3BucketNameEnum, String fileFullPath, String tagKey) {
        //获取标签数据
        GetObjectTaggingRequest getTagRequest = GetObjectTaggingRequest.builder()
                .bucket(s3BucketNameEnum.getBucketName())
                .key(fileFullPath)
                .build();
        GetObjectTaggingResponse getTagResponse = getS3client(s3BucketNameEnum).getObjectTagging(getTagRequest);
        List<Tag> tagList = getTagResponse.tagSet();
        if (CollectionUtils.isEmpty(tagList)) {
            return tagList;
        }
        //将标签数据剔除掉
        List<Tag> newTagList = Lists.newArrayList();
        for (Tag tag : tagList) {
            if (tag.key().equals(tagKey)) {
                continue;
            }
            newTagList.add(tag);
        }
        //直接删除所有标签
        delObjectAllTag(s3BucketNameEnum, fileFullPath);

        if (CollectionUtils.isNotEmpty(tagList) && CollectionUtils.isEmpty(newTagList)) {
            return tagList;
        }
        //重新设置标签
        PutObjectTaggingRequest putTagRequest = PutObjectTaggingRequest.builder()
                .bucket(s3BucketNameEnum.getBucketName())
                .key(fileFullPath)
                .tagging(Tagging.builder().tagSet(newTagList).build())
                .build();
        getS3client(s3BucketNameEnum).putObjectTagging(putTagRequest);
        return tagList;
    }

    /**
     * 查询文件对象的鉴黄结果
     *
     * @param s3BucketNameEnum
     * @param fileFullPath
     * @return
     */
    public Boolean getObjectNsfwResult(S3BucketNameEnum s3BucketNameEnum, String fileFullPath) {
        try {
            //获取标签数据
            GetObjectTaggingRequest getTagRequest = GetObjectTaggingRequest.builder()
                    .bucket(s3BucketNameEnum.getBucketName())
                    .key(fileFullPath)
                    .build();
            GetObjectTaggingResponse getTagResponse = getS3client(s3BucketNameEnum).getObjectTagging(getTagRequest);
            List<Tag> tagList = getTagResponse.tagSet();
            for (Tag tag : tagList) {
                if (Constants.NSFW_KEY.equals(tag.key()) && Boolean.valueOf(tag.value())) {
                    return true;
                }
            }
            //TODO 如果标签为空则进行手动鉴黄，并写上标签 进行兜底处理？？

        } catch (NoSuchKeyException e) {
            log.info("===> NoSuchKeyException : {}", e.getMessage());
            return false;
        }
        return null;
    }

    /**
     * 获取文件的头部信息数据（文件大小、文件类型等）
     * eg: AcceptRanges=bytes, LastModified=2021-10-11T06:56:03Z, ContentLength=584077, ETag="4ea19aa048f476b68f5a103173165775", ContentType=image/png, Metadata={}
     *
     * @param s3BucketNameEnum
     * @param fileFullPath
     * @return
     */
    public HeadObjectResponse getObjectHeader(S3BucketNameEnum s3BucketNameEnum, String fileFullPath) {
        try {
            HeadObjectRequest request = HeadObjectRequest.builder()
                    .bucket(s3BucketNameEnum.getBucketName())
                    .key(fileFullPath)
                    .build();
            HeadObjectResponse response = getS3client(s3BucketNameEnum).headObject(request);
            log.info("===> getObjectHeader response : {}", response);
            return response;
        } catch (Exception e) {
            log.error("===> getObjectHeader error : ", e);
        }
        return null;
    }

    /**
     * 获取文件的大小
     *
     * @param s3BucketNameEnum
     * @param fileFullPath
     * @return
     */
    public Long getObjectContentLength(S3BucketNameEnum s3BucketNameEnum, String fileFullPath) {
        HeadObjectResponse response = getObjectHeader(s3BucketNameEnum, fileFullPath);
        return response.contentLength();
    }

    /**
     * 获取文件类型
     *
     * @param fileSuffix
     * @return
     */
    public static String getContentType(String fileSuffix) {
        String result = ContentTypeEnum.getContentType(fileSuffix);
        return result == null ? "application/octet-stream" : result;
    }

}
