package com.testor.module.section.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.testor.biz.file.model.domain.SysFile;
import com.testor.biz.file.service.SysFileService;
import com.testor.common.constant.MinioConstant;
import com.testor.config.MinioProperties;
import com.testor.module.section.dao.TSysUploadTaskMapper;
import com.testor.module.section.model.dto.TaskInfoDTO;
import com.testor.module.section.model.dto.TaskRecordDTO;
import com.testor.module.section.model.entity.TSysUploadTask;
import com.testor.module.section.model.param.InitTaskParam;
import com.testor.module.section.service.SysUploadTaskService;
import com.tongtech.tfw.backend.common.context.ContextUtils;
import com.tongtech.tfw.backend.common.exception.BusinessException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 分片上传-分片任务记录(SysUploadTask)表服务实现类
 *
 * @since 2022-08-22 17:47:31
 */
@Service("sysUploadTaskService")
public class TSysUploadTaskServiceImpl extends ServiceImpl<TSysUploadTaskMapper, TSysUploadTask> implements SysUploadTaskService {

    @Resource
    private AmazonS3 amazonS3;

    @Resource
    private MinioProperties minioProperties;

    @Resource
    private TSysUploadTaskMapper sysUploadTaskMapper;

    @Value("${apisix.minioroute.url}")
    private String apisixUrl;

    @Autowired
    private SysFileService sysFileService;

    @Override
    public TSysUploadTask getByIdentifier(String identifier) {
        return sysUploadTaskMapper.selectOne(new QueryWrapper<TSysUploadTask>().lambda().eq(TSysUploadTask::getFileIdentifier, identifier));
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public TaskInfoDTO initTask(InitTaskParam param) throws BusinessException {
        String loginUserId = ContextUtils.getLoginUserId();
        if (loginUserId == null) {
            throw new BusinessException("请先登录");
        }
        if(param.getTotalSize() <= 0){
            throw new BusinessException("请勿上传空文件");
        }
        Date currentDate = new Date();
        String bucketName = minioProperties.getBucket();
        String fileName = param.getFileName();
        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
        String key = StrUtil.format("{}/{}.{}", DateUtil.format(currentDate, "YYYY-MM-dd"), IdUtil.randomUUID(), suffix);
        String contentType = MediaTypeFactory.getMediaType(key).orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(contentType);
        InitiateMultipartUploadResult initiateMultipartUploadResult = amazonS3
                .initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName, key).withObjectMetadata(objectMetadata));
        String uploadId = initiateMultipartUploadResult.getUploadId();

        TSysUploadTask task = new TSysUploadTask();
        int chunkNum = (int) Math.ceil(param.getTotalSize() * 1.0 / param.getChunkSize());
        task.setBucketName(minioProperties.getBucket())
                .setChunkNum(chunkNum)
                .setChunkSize(param.getChunkSize())
                .setTotalSize(param.getTotalSize())
                .setFileIdentifier(param.getIdentifier())
                .setFileName(fileName)
                .setObjectKey(key)
                .setUploadId(uploadId);
        sysUploadTaskMapper.insert(task);

        List<SysFile> files = sysFileService.list(new QueryWrapper<SysFile>().eq("file_id", task.getId()));
        if (files.size() == 0) {
            SysFile sysFile = new SysFile();
            sysFile.setFileId(task.getId());
            sysFile.setFileName(fileName);
            sysFile.setFileDowName(key);
            sysFile.setStatus("0");
            sysFile.setFileSize(BigDecimal.valueOf(param.getTotalSize()));
            sysFile.setFilePath(getPath(bucketName, key));
            sysFile.setFileExt(suffix);
            sysFile.setCreateBy(loginUserId);
            sysFile.setCreateDate(new Date());
            sysFileService.save(sysFile);
        }
        return new TaskInfoDTO().setId(task.getId()).setFinished(false).setTaskRecord(TaskRecordDTO.convertFromEntity(task)).setPath(getPath(bucketName, key));
    }

    @Override
    public String getPath(String bucket, String objectKey) {
        return StrUtil.format("{}/{}/{}", apisixUrl, bucket, objectKey);
    }

    @Override
    public TaskInfoDTO getTaskInfo(String identifier) {
        TSysUploadTask task = getByIdentifier(identifier);
        if (task == null) {
            return null;
        }
        TaskInfoDTO result = new TaskInfoDTO().setFinished(true).setTaskRecord(TaskRecordDTO.convertFromEntity(task)).setPath(getPath(task.getBucketName(), task.getObjectKey()));

        boolean doesObjectExist = amazonS3.doesObjectExist(task.getBucketName(), task.getObjectKey());
        if (!doesObjectExist) {
            // 未上传完，返回已上传的分片
            ListPartsRequest listPartsRequest = new ListPartsRequest(task.getBucketName(), task.getObjectKey(), task.getUploadId());
            PartListing partListing = amazonS3.listParts(listPartsRequest);
            result.setFinished(false).getTaskRecord().setExitPartList(partListing.getParts());
        }
        return result;
    }

    @Override
    public String genPreSignUploadUrl(String bucket, String objectKey, Map<String, String> params) {
        Date currentDate = new Date();
        Date expireDate = DateUtil.offsetMillisecond(currentDate, MinioConstant.PRE_SIGN_URL_EXPIRE.intValue());
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, objectKey)
                .withExpiration(expireDate).withMethod(HttpMethod.PUT);
        if (params != null) {
            params.forEach((key, val) -> request.addRequestParameter(key, val));
        }
        URL preSignedUrl = amazonS3.generatePresignedUrl(request);
        String presignedObjectUrl = preSignedUrl.toString();
        if (StringUtils.isNotBlank(minioProperties.getPath())) {//如果线上环境配置了域名解析，可以进行替换
            presignedObjectUrl = presignedObjectUrl.replace(minioProperties.getEndpoint(),minioProperties.getPath());
        }
        return presignedObjectUrl;
    }

    @Override
    public void merge(String identifier) {
        TSysUploadTask task = getByIdentifier(identifier);
        if (task == null) {
            throw new RuntimeException("分片任务不存");
        }

        ListPartsRequest listPartsRequest = new ListPartsRequest(task.getBucketName(), task.getObjectKey(), task.getUploadId());
        PartListing partListing = amazonS3.listParts(listPartsRequest);
        List<PartSummary> parts = partListing.getParts();
        if (!task.getChunkNum().equals(parts.size())) {
            // 已上传分块数量与记录中的数量不对应，不能合并分块
            throw new RuntimeException("分片缺失，请重新上传");
        }
        CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest()
                .withUploadId(task.getUploadId())
                .withKey(task.getObjectKey())
                .withBucketName(task.getBucketName())
                .withPartETags(parts.stream().map(partSummary -> new PartETag(partSummary.getPartNumber(), partSummary.getETag())).collect(Collectors.toList()));
        CompleteMultipartUploadResult result = amazonS3.completeMultipartUpload(completeMultipartUploadRequest);

    }
}
