package com.qmth.themis.business.util;

import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.*;
import com.qmth.themis.business.constant.SystemConstant;
import com.qmth.themis.business.domain.AliYunOssDomain;
import com.qmth.themis.business.domain.AliYunOssPrivateDomain;
import com.qmth.themis.business.domain.AliYunOssPublicDomain;
import com.qmth.themis.business.domain.SysDomain;
import com.qmth.themis.business.enums.UploadFileEnum;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * @Description: oss工具
 * @Param:
 * @return:
 * @Author: wangliang
 * @Date: 2020/4/4
 */
@Component
public class OssUtil {

    private final static Logger log = LoggerFactory.getLogger(OssUtil.class);

    private AliYunOssPublicDomain aliYunOssPublicDomain;

    private OSS publicClient;

    private OSS privateClient;

    private OSS privateUrlClient;

    private AliYunOssPrivateDomain aliYunOssPrivateDomain;

    @Bean
    public AliYunOssPublicDomain publicOssEnv(AliYunOssDomain aliYunOssDomain, SysDomain sysDomain) {
        aliYunOssPublicDomain = new AliYunOssPublicDomain(aliYunOssDomain.getPublicEndpoint(),
                aliYunOssDomain.getPublicName(), aliYunOssDomain.getPublicAccessKeyId(),
                aliYunOssDomain.getPublicAccessKeySecret(), aliYunOssDomain.getPublicBucket(),
                aliYunOssDomain.getPublicUrl(), sysDomain.isOss());
        publicClient = new OSSClientBuilder()
                .build(aliYunOssDomain.getPublicEndpoint(), aliYunOssDomain.getPublicAccessKeyId(),
                        aliYunOssDomain.getPublicAccessKeySecret());
        return aliYunOssPublicDomain;
    }

    @Bean
    public AliYunOssPrivateDomain privateOssEnv(AliYunOssDomain aliYunOssDomain, SysDomain sysDomain) {
        aliYunOssPrivateDomain = new AliYunOssPrivateDomain(aliYunOssDomain.getPrivateEndpoint(),
                aliYunOssDomain.getPrivateName(), aliYunOssDomain.getPrivateAccessKeyId(),
                aliYunOssDomain.getPrivateAccessKeySecret(), aliYunOssDomain.getPrivateBucket(),
                aliYunOssDomain.getPrivateUrl(), sysDomain.isOss());
        privateClient = new OSSClientBuilder()
                .build(aliYunOssDomain.getPrivateEndpoint(), aliYunOssDomain.getPrivateAccessKeyId(),
                        aliYunOssDomain.getPrivateAccessKeySecret());

        ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
        conf.setSupportCname(true);
        privateUrlClient = new OSSClientBuilder()
                .build(aliYunOssDomain.getPrivateUrl(), aliYunOssDomain.getPrivateAccessKeyId(),
                        aliYunOssDomain.getPrivateAccessKeySecret(), conf);
        return aliYunOssPrivateDomain;
    }

    public AliYunOssPublicDomain getAliYunOssPublicDomain() {
        return aliYunOssPublicDomain;
    }

    public AliYunOssPrivateDomain getAliYunOssPrivateDomain() {
        return aliYunOssPrivateDomain;
    }

    public boolean isPublic(UploadFileEnum type) {
        return !UploadFileEnum.file.equals(type);
    }

    /**
     * oss上传数据流
     *
     * @param isPublic
     * @param objectName
     * @param inputStream
     * @param md5
     */
    public void upload(boolean isPublic, String objectName, InputStream inputStream, String md5) {
        log.info("oss  upload stream is come in");
        String bucket = isPublic ? aliYunOssPublicDomain.getPublicBucket() : aliYunOssPrivateDomain.getPrivateBucket();
        OSS client = isPublic ? publicClient : privateClient;
        ObjectMetadata meta = new ObjectMetadata();
        if (Objects.nonNull(md5)) {
            // 设置MD5校验。
            meta.setContentMD5(md5);
        }
        // 上传内容到指定的存储空间（bucketName）并保存为指定的文件名称（objectName）。
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, objectName, inputStream);
        PutObjectResult por = client.putObject(putObjectRequest);
        log.info("objectName:{},requestid:{}", objectName, por.getRequestId());
    }

    /**
     * oss上传本地文件
     *
     * @param isPublic
     * @param objectName
     * @param file
     */
    public void upload(boolean isPublic, String objectName, File file) throws IOException {
        log.info("oss  upload file is come in");
//        long start = System.currentTimeMillis();
        String bucket = isPublic ? aliYunOssPublicDomain.getPublicBucket() : aliYunOssPrivateDomain.getPrivateBucket();
        OSS client = isPublic ? publicClient : privateClient;
        ObjectMetadata meta = new ObjectMetadata();
        // 设置MD5校验。
        String md5 = BinaryUtil.toBase64String(BinaryUtil.calculateMd5(FileUtils.readFileToByteArray(file)));
        meta.setContentMD5(md5);
        // 上传内容到指定的存储空间（bucketName）并保存为指定的文件名称（objectName）。
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, objectName, file, meta);
        PutObjectResult por = client.putObject(putObjectRequest);
        log.info("objectName:{},requestid:{}", objectName, por.getRequestId());
//        long end = System.currentTimeMillis();
//        System.out.println("总耗时：" + (end - start) / 1000 + "秒");
    }

    /**
     * oss断点续传文件
     *
     * @param isPublic
     * @param objectName
     * @param file
     */
    public void uploadPart(boolean isPublic, String objectName, File file) throws IOException {
        log.info("oss uploadPart file is come in");
        long start = System.currentTimeMillis();
        String bucket = isPublic ? aliYunOssPublicDomain.getPublicBucket() : aliYunOssPrivateDomain.getPrivateBucket();
        OSS client = isPublic ? publicClient : privateClient;
        try {
            ObjectMetadata meta = new ObjectMetadata();
//             指定上传的内容类型。
//            meta.setContentType("text/plain");
            // 文件上传时设置访问权限ACL。
            // meta.setObjectAcl(CannedAccessControlList.Private);
            // 通过UploadFileRequest设置多个参数。
            // 依次填写Bucket名称（例如examplebucket）以及Object完整路径（例如exampledir/exampleobject.txt），Object完整路径中不能包含Bucket名称。
            UploadFileRequest uploadFileRequest = new UploadFileRequest(bucket, objectName);
//                    .withProgressListener(new test());

            // 通过UploadFileRequest设置单个参数。
            // 填写本地文件的完整路径，例如D:\\localpath\\examplefile.txt。如果未指定本地路径，则默认从示例程序所属项目对应本地路径中上传文件。
            uploadFileRequest.setUploadFile(file.getPath());
            // 指定上传并发线程数，默认值为1。
            uploadFileRequest.setTaskNum(5);
            // 指定上传的分片大小，单位为字节，取值范围为100 KB~5 GB。默认值为100 KB。
            uploadFileRequest.setPartSize(1 * 1024 * 1024);
            // 开启断点续传，默认关闭。
            uploadFileRequest.setEnableCheckpoint(true);
            // 记录本地分片上传结果的文件。上传过程中的进度信息会保存在该文件中，如果某一分片上传失败，再次上传时会根据文件中记录的点继续上传。上传完成后，该文件会被删除。
            // 如果未设置该值，默认与待上传的本地文件同路径，名称为${uploadFile}.ucp。
//            uploadFileRequest.setCheckpointFile(SystemConstant.TEMP_FILES_DIR);
            // 文件的元数据。
            uploadFileRequest.setObjectMetadata(meta);
            // 设置上传回调，参数为Callback类型。
//            uploadFileRequest.setCallback(t.callback());
            // 断点续传上传。
            client.uploadFile(uploadFileRequest);
            long end = System.currentTimeMillis();
            System.out.println("总耗时：" + (end - start) / 1000 + "秒");
        } catch (Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
        } catch (Throwable ce) {
            log.error(SystemConstant.LOG_ERROR, ce);
        }
    }

    /**
     * oss上传数据内容
     *
     * @param isPublic
     * @param objectName
     * @param content
     */
    public void upload(boolean isPublic, String objectName, String content) {
        log.info("oss public upload string is come in");
        String bucket = isPublic ? aliYunOssPublicDomain.getPublicBucket() : aliYunOssPrivateDomain.getPrivateBucket();
        OSS client = isPublic ? publicClient : privateClient;
        ObjectMetadata meta = new ObjectMetadata();
        // 设置MD5校验。
        String md5 = BinaryUtil.toBase64String(BinaryUtil.calculateMd5(content.getBytes(StandardCharsets.UTF_8)));
        meta.setContentMD5(md5);
        // 上传内容到指定的存储空间（bucketName）并保存为指定的文件名称（objectName）。
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, objectName,
                new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), meta);
        PutObjectResult por = client.putObject(putObjectRequest);
        log.info("objectName:{},requestid:{}", objectName, por.getRequestId());
    }

    /**
     * 从oss上下载文件到本地
     *
     * @param isPublic
     * @param localPath
     * @throws IOException
     */
    public File download(boolean isPublic, String objectName, String localPath) {
        log.info("oss Download is come in");
        String bucket = isPublic ? aliYunOssPublicDomain.getPublicBucket() : aliYunOssPrivateDomain.getPrivateBucket();
        OSS client = isPublic ? publicClient : privateClient;
        File localFile = new File(localPath);
        if (!localFile.getParentFile().exists()) {
            localFile.getParentFile().mkdirs();
        }
        client.getObject(new GetObjectRequest(bucket, objectName), localFile);
        return localFile;
    }

    /**
     * 从oss上下载文件到byte[]
     *
     * @param isPublic
     * @throws IOException
     */
    public byte[] download(boolean isPublic, String objectName) throws IOException {
        log.info("oss Download is come in");
        String bucket = isPublic ? aliYunOssPublicDomain.getPublicBucket() : aliYunOssPrivateDomain.getPrivateBucket();
        OSS client = isPublic ? publicClient : privateClient;
        OSSObject obj = client.getObject(new GetObjectRequest(bucket, objectName));
        byte[] data = IOUtils.toByteArray(obj.getObjectContent());
        obj.close();
        return data;
    }

    /**
     * oss删除文件
     *
     * @param isPublic
     * @param objectName
     */
    public void delete(boolean isPublic, String objectName) {
        String bucket = isPublic ? aliYunOssPublicDomain.getPublicBucket() : aliYunOssPrivateDomain.getPrivateBucket();
        OSS client = isPublic ? publicClient : privateClient;
        // 删除文件。如需删除文件夹，请将ObjectName设置为对应的文件夹名称。如果文件夹非空，则需要将文件夹下的所有object删除后才能删除该文件夹。
        client.deleteObject(bucket, objectName);
    }

    /**
     * oss批量删除文件
     *
     * @param isPublic
     * @param paths
     */
    public void batchDelete(boolean isPublic, List<String> paths) {
        String bucket = isPublic ? aliYunOssPublicDomain.getPublicBucket() : aliYunOssPrivateDomain.getPrivateBucket();
        OSS client = isPublic ? publicClient : privateClient;
        // 删除文件。key等同于ObjectName，表示删除OSS文件时需要指定包含文件后缀在内的完整路径，例如abc/efg/123.jpg。
        DeleteObjectsResult deleteObjectsResult = client
                .deleteObjects(new DeleteObjectsRequest(bucket).withKeys(paths));
        deleteObjectsResult.getDeletedObjects();
    }

    /**
     * 获取私有bucket文件访问url，五分钟有效
     *
     * @param objectPath
     * @return
     */
    public String getPrivateUrl(String objectPath) {
        Date expiration = new Date(System.currentTimeMillis() + 5 * 60 * 1000);
        GeneratePresignedUrlRequest generatePresignedUrlRequest;
        generatePresignedUrlRequest = new GeneratePresignedUrlRequest(aliYunOssPrivateDomain.getPrivateBucket(),
                objectPath);
        generatePresignedUrlRequest.setExpiration(expiration);
        URL url = privateUrlClient.generatePresignedUrl(generatePresignedUrlRequest);
        return url.toString();
    }
}
