package com.backendsys.modules.sdk.tencentcloud.cos.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.backendsys.exception.CustException;
import com.backendsys.modules.common.config.security.utils.SecurityUtil;
import com.backendsys.modules.common.utils.CommonUtil;
import com.backendsys.modules.sdk.tencentcloud.cos.entity.Progress;
import com.backendsys.modules.sdk.tencentcloud.cos.service.TencentCosService;
import com.backendsys.modules.sdk.tencentcloud.cos.utils.TencentCosUtil;
import com.backendsys.modules.sse.entity.SseResponse;
import com.backendsys.modules.sse.entity.SseResponseEnum;
import com.backendsys.modules.sse.utils.SseUtil;
import com.backendsys.modules.upload.entity.SysFileResult;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.exception.MultiObjectDeleteException;
import com.qcloud.cos.http.HttpMethodName;
import com.qcloud.cos.model.*;
import com.qcloud.cos.model.ciModel.common.ImageProcessRequest;
import com.qcloud.cos.model.ciModel.persistence.CIUploadResult;
import com.qcloud.cos.model.ciModel.persistence.PicOperations;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.transfer.Transfer;
import com.qcloud.cos.transfer.TransferManager;
import com.qcloud.cos.transfer.TransferProgress;
import com.qcloud.cos.transfer.Upload;
import com.qcloud.cos.utils.Jackson;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.math.BigDecimal;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

// 图片处理概述
// https://cloud.tencent.com/document/product/436/42215
// https://cloud.tencent.com/document/product/460/6929
// https://github.com/tencentyun/cos-java-sdk-v5/blob/master/src/main/java/com/qcloud/cos/demo/ci/BasicImageProcessing.java
@Service
public class TencentCosServiceImpl implements TencentCosService {

    @Autowired
    private SseUtil sseUtil;

    @Value("${spring.application.name}")
    private String APPLICATION_NAME;
    @Value("${tencent.cos.max-size}")
    private long MAX_SIZE;
    @Value("${tencent.cos.region}")
    private String REGION;
    @Value("${tencent.cos.bucket-name}")
    private String BUCKET_NAME;
    @Value("${tencent.cos.accessible-domain}")
    private String ACCESSIBLE_DOMAIN;
    @Value("${tencent.cos.secret-id}")
    private String SECRET_ID;
    @Value("${tencent.cos.secret-key}")
    private String SECRET_KEY;


    // [腾讯云COS] 生成cos客户端
    private COSClient getClient() {
        COSCredentials cred = new BasicCOSCredentials(SECRET_ID, SECRET_KEY);
        Region region1 = new Region(REGION);
        ClientConfig clientConfig = new ClientConfig(region1);
        clientConfig.setRegion(region1);
        clientConfig.setPrintShutdownStackTrace(false);
        COSClient cosClient = new COSClient(cred, clientConfig);
        return cosClient;
    }

    // [腾讯云COS][高级接口] 获取进度函数
    private void showTransferProgress(String filename, Transfer transfer, Long input_user_id) {

        Long user_id = input_user_id == null ? SecurityUtil.getUserId() : input_user_id;
        System.out.println("showTransferProgress (user_id): " + user_id);

        // [SSE] 进度回传
        Progress progress = new Progress();
        progress.setState("init");
        progress.setFilename(filename);
        sseUtil.send(user_id, (new SseResponse(SseResponseEnum.UPLOAD, progress)).toJsonStr());

        // 查询上传是否已经完成
        while (transfer.isDone() == false) {
            // 每秒获取一次进度
            try { Thread.sleep(1000); } catch (InterruptedException e) { return; }

            TransferProgress transferProgress = transfer.getProgress();
            long current = transferProgress.getBytesTransferred();
            long total = transferProgress.getTotalBytesToTransfer();
            BigDecimal percent = NumberUtil.round(transferProgress.getPercentTransferred(), 2);
            String state = Convert.toStr(transfer.getState());
            System.out.printf("Upload progress: [%d / %d] = %.02f%% (%s)\n", current, total, percent, state);

            // [SSE] 进度回传
            progress.setCurrent(current);
            progress.setTotal(total);
            progress.setPercent(percent);
            progress.setState(state.toLowerCase());
            String dataStr = (new SseResponse(SseResponseEnum.UPLOAD, progress)).toJsonStr();
            sseUtil.send(user_id, dataStr);

            // state: (完成 Completed, 失败 Failed)
            // System.out.println(transfer.getState());
        }
    }

    /**
     * [腾讯云COS] 上传对象
     * - 如果对象是图片，则同时产生缩略图
     */
    @Override
    public SysFileResult putObject(MultipartFile multipartFile, String object_key, Integer is_watermark) {

        if (multipartFile.isEmpty()) throw new CustException("file 上传文件不能为空");

        // 生成 UUID 文件名
        String filename = CommonUtil.generateUUIDFilename(multipartFile);

        // 如果不传 object_key，则默认使用临时文件夹 (/temp/UUID文件名)
        if (StrUtil.isEmpty(object_key)) object_key = "temp/" + filename;
        System.out.println("Upload object: " + object_key);

        // ------------------------------------------------------------------------------

        // 初始化 CosClient
        COSClient cosClient = getClient();
        InputStream inputStream = null;
        try {

            // [配置]
            ObjectMetadata metadata = new ObjectMetadata();
            // 设置流大小 (只有设置了才有进度)
            metadata.setContentLength(multipartFile.getSize());

//            // [图片处理] -----------------------------------------------------------
////            // 设置请求头规则 (基础图片处理) (当  fileid 与 ObjectKey 相同时，处理后的图片将覆盖原图存储；不相同时将分别存储原图和处理后图片)
////            // https://cloud.tencent.com/document/product/460/6929
////            // https://github.com/tencentyun/cos-java-sdk-v5/blob/master/src/main/java/com/qcloud/cos/demo/ci/BasicImageProcessing.java
//            /*
//                [支持处理的原图格式]
//                - 普通格式: jpg/jpeg、png、bmp、webp、tiff、gif
//                - 高级格式: avif、heif/heic、tpg、apng、psd、svg、raw、ai、eps、dng、cr3、crw、mos、erf、3fr、fff、kdc、dcr、rw2、pef、sr2、srf、arw、nef、nrw、orf、mef、mrw
//             */
//            // 判断图片类型
//            List<String> imageType = Arrays.asList("jpg", "jpeg", "png", "bmp", "webp", "tiff", "gif");
//            boolean isImage = imageType.contains(CommonUtil.getFilenameSuffix(multipartFile, false).toLowerCase());
//            if (isImage) {
//                // 选用样式1，并限定缩略图的宽高最小值为100×100 绝对质量为60
//                String rule = "imageView2/1/w/100/h/100/q/60";
////                String rule = "imageMogr2/thumbnail/!50p";
//                String object_key_thumb = "/" + FilenameUtil.insertThumbSuffix(object_key);
//                String object_key_thumb_encode = URLEncoder.encode(object_key_thumb, "UTF-8");
//                String picOperations = "{\"is_pic_info\":1,\"rules\":[{\"fileid\":\"" + object_key_thumb_encode + "\",\"rule\":\"" + rule + "\"}]}";
//                System.out.println("picOperations = " + picOperations);
//                metadata.setHeader("Pic-Operations", picOperations);
//            }
//            // --------------------------------------------------------------------

            // 创建 PutObjectRequest 对象，PutObject 请求。(异步)
            inputStream = multipartFile.getInputStream();
            PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, object_key, inputStream, metadata);

//            // 普通上传 (没有进度)
//            PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);


            // 高级上传
            TransferManager transferManager = TencentCosUtil.createTransferManager(cosClient);
            Upload upload = transferManager.upload(putObjectRequest);   // 返回一个异步结果Upload
            showTransferProgress(filename, upload, null);    // 查询上传进度，直到上传结束
            UploadResult uploadResult = upload.waitForUploadResult();   // 捕获可能出现的异常

            System.out.println("[腾讯云][cos-uploadResult] = " + uploadResult.getDateStr());

            // 判断是图片类型
            List<String> imageType = Arrays.asList("jpg", "jpeg", "png", "bmp", "webp", "tiff", "gif");
            boolean isImage = imageType.contains(CommonUtil.getFilenameSuffix(multipartFile, false).toLowerCase());
            if (isImage) {
                // 打水印 (如果是图片)
                if (is_watermark != null && is_watermark == 1) {
                    addWatermask(object_key);
                }
            }

            // 自定义返回结果实体
            SysFileResult result = new SysFileResult();
            result.setKey(object_key);
//            result.setRequest_id(uploadResult.getRequestId());
//            result.setE_tag(uploadResult.getETag());
            result.setDomain(ACCESSIBLE_DOMAIN);
            result.setUrl(ACCESSIBLE_DOMAIN + "/" + object_key);
            result.setTarget(1);
            return result;

        } catch (IOException e) {
            throw new CustException(e.getMessage());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // 忽略关闭流时的异常
                }
            }
            if (cosClient != null) cosClient.shutdown();
        }

    }

    // [腾讯云COS] 打水印
    public void addWatermask(String object_key) {
        // 初始化 CosClient
        COSClient cosClient = getClient();
        InputStream inputStream = null;
        try {

            // 对原图发起云上处理
            ImageProcessRequest request = new ImageProcessRequest(BUCKET_NAME, object_key);

            PicOperations picOperations = new PicOperations();
            picOperations.setIsPicInfo(1);
            List<PicOperations.Rule> rule_list = new ArrayList<>();

            PicOperations.Rule rule_item = new PicOperations.Rule();

            // -- 水印 --------------------------------
            // 文字水印：https://cloud.tencent.com/document/product/436/119425
            // 上传时处理：https://cloud.tencent.com/document/product/460/18147#.E4.BA.91.E4.B8.8A.E6.95.B0.E6.8D.AE.E5.A4.84.E7.90.86
            String text = "稻谷文化";
            byte[] textBates = text.getBytes(StandardCharsets.UTF_8);
            String encodedText = Base64.getUrlEncoder().encodeToString(textBates).replaceAll("=", "");
            String rule = "watermark/2/text/" + encodedText +
                    "/fill/IzNEM0QzRA/fontsize/28/dissolve/50/batch/1/spacing/40/degree/35";
            // - dissolve： 文字透明度，取值1 - 100 ，默认90
            // - gravity: 九宫格位置 (gravity/northeast/)
            // - dx: 水平（横轴）边距，单位为像素，缺省值为0
            // - dy: 垂直（纵轴）边距，单位为像素，默认值为0
            // - batch: 平铺水印功能，可将文字水印平铺至整张图片。值为1时，表示开启平铺水印功能
            // - spacing: 平铺模式下的水平、垂直间距相对文字水印贴图的宽高百分比，范围为[0,100]，默认10
            // - degree: 文字水印的旋转角度设置，取值范围为0 - 360，默认0


            rule_item.setBucket(BUCKET_NAME);
            rule_item.setFileId("/" + object_key);  // 以 '/' 开头是为绝对路径，否则会在当前目录下再创建一个对象路径
            rule_item.setRule(rule);
            // ----------------------------------

            rule_list.add(rule_item);
            picOperations.setRules(rule_list);

            System.out.println("Jackson.toJsonString(picOperations) = " + Jackson.toJsonString(picOperations));

            request.setPicOperations(picOperations);
            CIUploadResult ciUploadResult = cosClient.processImage(request);
            System.out.println("[腾讯云][cos-ciUploadResult] OriginalInfo= " + Jackson.toJsonString(ciUploadResult.getOriginalInfo()));
            System.out.println("[腾讯云][cos-ciUploadResult] ProcessResults = " + Jackson.toJsonString(ciUploadResult.getProcessResults()));

        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // 忽略关闭流时的异常
                }
            }
            if (cosClient != null) cosClient.shutdown();
        }
    }

    // [腾讯云COS] 查询对象是否存在
    @Override
    public boolean doesObjectExist(String object_key) {
        COSClient cosClient = getClient();
        boolean isExist = cosClient.doesObjectExist(BUCKET_NAME, object_key);
        cosClient.shutdown();
        return isExist;
    }

    // [腾讯云COS] 删除对象
    @Override
    public void deleteObject(String object_key) {
        COSClient cosClient = getClient();
        cosClient.deleteObject(BUCKET_NAME, object_key);
        cosClient.shutdown();
    }

    // [腾讯云COS] 批量删除对象
    // https://cloud.tencent.com/document/product/436/65939#841fe310-bdf8-4789-9bc0-26ea844e316d
    @Override
    public void deleteObjects(List<String> object_keys) {
        COSClient cosClient = getClient();

        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(BUCKET_NAME);
        List<DeleteObjectsRequest.KeyVersion> keyList = new ArrayList<>();
        object_keys.stream().forEach(object_key -> {
            keyList.add(new DeleteObjectsRequest.KeyVersion(object_key));
        });
        deleteObjectsRequest.setKeys(keyList);

        System.out.println("[腾讯云Cos][即将删除对象]: " +
                keyList.stream()
                .map(obj -> obj.getKey())
                .collect(Collectors.joining(", ", "[", "]")));

        DeleteObjectsResult deleteObjectsResult = cosClient.deleteObjects(deleteObjectsRequest);
        List<DeleteObjectsResult.DeletedObject> deleteObjectResultArray = deleteObjectsResult.getDeletedObjects();

        System.out.println("[腾讯云Cos][批量删除对象成功]: " +
                deleteObjectResultArray.stream()
                .map(obj -> obj.getKey())
                .collect(Collectors.joining(", ", "[", "]")));

        cosClient.shutdown();
    }


    // [腾讯云COS] 生成临时图片地址
    @Override
    public URL generateUrl(String object_key, Integer minutes) {
        // 生成临时访问的URL (15分钟)
        COSClient cosClient = getClient();
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(BUCKET_NAME, object_key, HttpMethodName.GET);
        Date expirationDate = new DateTime().plusMinutes(minutes).toDate();
        request.setExpiration(expirationDate);
        URL url = cosClient.generatePresignedUrl(request);
        cosClient.shutdown();
        return url;
    }

    // [腾讯云COS] 初始化分块上传
    // https://cloud.tencent.com/document/product/436/65935#67d1955e-8784-4b9e-b12e-4f28f701f7eb
    @Override
    public InitiateMultipartUploadResult initiateMultipartUpload(String filename) {

        COSClient cosClient = getClient();
        try {

            // 使用临时文件夹 (/temp) + UUID文件名
            String object_key = "temp/" + filename;
            System.out.println("initiateMultipartUpload object: " + object_key);

//            long dataSize = multipartFile.getSize();
//            long partSize = 524288; // 512kb
//            System.out.println("dataSize = " + dataSize + ", partSize = " + partSize);
//            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(BUCKET_NAME, object_key, dataSize, partSize);
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(BUCKET_NAME, object_key);
            InitiateMultipartUploadResult initResult = cosClient.initiateMultipartUpload(request);
            return initResult;

        } catch (CosServiceException e) {
            throw new CustException(e.getMessage());
        } catch (CosClientException e) {
            throw new CustException(e.getMessage());
        } finally {
            if (cosClient != null) cosClient.shutdown();
        }
    }

    // [腾讯云COS] 上传分块
    @Override
    public UploadPartResult uploadPart(MultipartFile multipartFile, String upload_id, String object_key, Integer partNumber) {
        COSClient cosClient = getClient();
        InputStream inputStream = null;
        try {

            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(BUCKET_NAME);
            uploadPartRequest.setKey(object_key);
            uploadPartRequest.setUploadId(upload_id);

            inputStream = multipartFile.getInputStream();
            uploadPartRequest.setInputStream(inputStream);
            uploadPartRequest.setPartSize(multipartFile.getSize());     // 设置分块的长度
            uploadPartRequest.setPartNumber(partNumber);                // 设置要上传的分块编号，从 1 开始
            return cosClient.uploadPart(uploadPartRequest);

        } catch (IOException | CosClientException e) {
            throw new CustException(e.getMessage());
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // 忽略关闭流时的异常
                }
            }
            if (cosClient != null) cosClient.shutdown();
        }
    }

    // [腾讯云COS] 完成分块上传
    @Override
    public CompleteMultipartUploadResult completeMultipartUpload(String upload_id, String object_key, List<PartETag> partETags, Integer is_watermark) {

        if (StrUtil.isEmpty(upload_id)) throw new CustException("upload_id 不能为空");
        if (StrUtil.isEmpty(object_key)) throw new CustException("object_key 不能为空");

        COSClient cosClient = getClient();
        try {

            // 分片上传结束后，调用 complete 完成分片上传
            CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(BUCKET_NAME, object_key, upload_id, partETags);
            CompleteMultipartUploadResult result = cosClient.completeMultipartUpload(completeMultipartUploadRequest);

            // 判断是图片类型 (这里只能判断后缀名)
            String suffix = FileNameUtil.getSuffix(object_key);
            List<String> imageType = Arrays.asList("jpg", "jpeg", "png", "bmp", "webp", "tiff", "gif");
            boolean isImage = imageType.contains(suffix.toLowerCase());
            if (isImage) {
                // 打水印 (如果是图片)
                if (is_watermark != null && is_watermark == 1) {
                    addWatermask(object_key);
                }
            }

            return result;

        } catch (CosClientException e) {
            throw new CustException(e.getMessage());
        } finally {
            if (cosClient != null) cosClient.shutdown();
        }
    }


    // [腾讯云COS] 查询已上传的分块
    @Override
    public PartListing listParts(String upload_id, String object_key) {

        if (StrUtil.isEmpty(upload_id)) throw new CustException("upload_id 不能为空");
        if (StrUtil.isEmpty(object_key)) throw new CustException("object_key 不能为空");

        COSClient cosClient = getClient();
        try {

            // 用于保存已上传的分片信息
            List<PartETag> partETags = new LinkedList<>();
            PartListing partListing = null;
            ListPartsRequest listPartsRequest = new ListPartsRequest(BUCKET_NAME, object_key, upload_id);

            do {
                partListing = cosClient.listParts(listPartsRequest);
                for (PartSummary partSummary : partListing.getParts()) {
                    partETags.add(new PartETag(partSummary.getPartNumber(), partSummary.getETag()));
                    System.out.println("list multipart upload parts, partNum:" + partSummary.getPartNumber() + ", etag:" + partSummary.getETag());
                }
                listPartsRequest.setPartNumberMarker(partListing.getNextPartNumberMarker());
                return partListing;
            } while (partListing.isTruncated());

        } finally {
            if (cosClient != null) cosClient.shutdown();
        }
    }


    // [腾讯云COS] URL 转存@Override
    @Override
    public SysFileResult urlToCOS(String url) {
        return urlToCOS(url, null);
    }
    @Override
    public SysFileResult urlToCOS(String url, String input_suffix) {
        return urlToCOS(url, input_suffix);
    }
    @Override
    public SysFileResult urlToCOS(String url, String input_suffix, Long user_id) {

        if (StrUtil.isEmpty(url)) throw new CustException("url 不能为空");

        // 下载URL文件到本地
        File downloadFile = CommonUtil.downloadLocalFromUrl(url);
        // 获得URL文件后缀名
        String suffix = StrUtil.isEmpty(input_suffix) ? CommonUtil.getFileSuffixFromUrl(url) : input_suffix;
        // 获得文件大小
        Long size = downloadFile.length();

        // 使用临时文件夹 (/temp) + UUID文件名.后缀名
        String object_filename = CommonUtil.generateUUIDFilename(suffix);
        String object_key = "temp/" + object_filename;
        System.out.println("initiateMultipartUpload object: " + object_key);

        COSClient cosClient = getClient();
        try{

            PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, object_key, downloadFile);
            PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
            System.out.println("putObjectResult: " + putObjectResult.toString());

            // 高级上传
            TransferManager transferManager = TencentCosUtil.createTransferManager(cosClient);
            Upload upload = transferManager.upload(putObjectRequest);   // 返回一个异步结果Upload
            showTransferProgress(object_filename, upload, user_id);     // 查询上传进度，直到上传结束
            UploadResult uploadResult = upload.waitForUploadResult();   // 捕获可能出现的异常

            SysFileResult result = new SysFileResult();
            result.setDomain(ACCESSIBLE_DOMAIN);
            result.setKey(object_key);
            result.setRequest_id(uploadResult.getRequestId());
            result.setE_tag(uploadResult.getETag());
            result.setSize(size);
            result.setUrl(ACCESSIBLE_DOMAIN + "/" + object_key);
            result.setTarget(1);
            return result;

        } catch (CosClientException e) {
            throw new CustException("CosClientException: " + e.getMessage());
        } catch (InterruptedException e) {
            throw new CustException("InterruptedException: " + e.getMessage());
        } finally {
            if (cosClient != null) cosClient.shutdown();
        }
    }

}
