package org.springblade.file.service.impl;

import cn.hutool.core.net.URLEncodeUtil;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.internal.Constants;
import com.obs.services.internal.utils.ServiceUtils;
import com.obs.services.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.file.config.ObsProperties;
import org.springblade.file.pojo.vo.PartEtagVO;
import org.springblade.file.service.IFileService;
import org.springblade.file.service.support.DownloadProgressListener;
import org.springblade.file.service.support.UploadProgressListener;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;

/**
 *  华为obs对象存储
 * @author bfhuange
 * @date 2024/10/21
 */
@Slf4j
@RequiredArgsConstructor
public class ObsFileServiceImpl implements IFileService {
    private final ObsProperties properties;
    private final ObsClient obsClient;

    @Override
    public void uploadFile(String objectKey, InputStream is) {
        this.uploadFile(objectKey, null, is, null);
    }

    @Override
    public String uploadTempFile(String objectKey, InputStream is, Integer expires) {
        // 上传文件
        this.uploadFile(objectKey, null, is, expires);
        // 生成下载链接
        return this.getFileUrl(objectKey, properties.getExpires());
    }

    @Override
    public void uploadFile(String objectKey, File file) {
        this.uploadFile(objectKey, file, null, null);
    }

    @Override
    public String initiateMultipartUpload(String objectKey) {
        InitiateMultipartUploadRequest initiateRequest = new InitiateMultipartUploadRequest(properties.getBucketName(), objectKey);
        try {
            InitiateMultipartUploadResult initiateResult = obsClient.initiateMultipartUpload(initiateRequest);
            return initiateResult.getUploadId();
        } catch (Exception e) {
            log.error("初始化分段上传异常", e);
            throw new ServiceException("初始化分段上传异常");
        }
    }

    @Override
    public PartEtagVO uploadPart(String objectKey, String uploadId, File file, int partNumber, Long offset, Long partSize) {
        UploadPartRequest uploadPartRequest = new UploadPartRequest();
        uploadPartRequest.setBucketName(properties.getBucketName());
        uploadPartRequest.setObjectKey(objectKey);
        uploadPartRequest.setUploadId(uploadId);
        uploadPartRequest.setPartNumber(partNumber);
        uploadPartRequest.setFile(file);
        if (offset != null) {
            uploadPartRequest.setOffset(offset);
        }
        if (partSize != null) {
            uploadPartRequest.setPartSize(partSize);
        }
        try {
            UploadPartResult uploadPartResult = obsClient.uploadPart(uploadPartRequest);
            return new PartEtagVO(uploadPartResult.getEtag(), uploadPartResult.getPartNumber());
        } catch (Exception e) {
            log.error("分段上传异常", e);
            throw new ServiceException("分段上传异常");
        }
    }

    @Override
    public void completeMultipartUpload(String objectKey, String uploadId, List<PartEtagVO> partEtagVOS) {
        if (CollectionUtil.isEmpty(partEtagVOS)) {
            log.warn("完成分段上传，要合并的分段列表为空");
            return;
        }
        // 参数转换
        List<PartEtag> list = partEtagVOS.stream()
                .map(partEtagVO -> new PartEtag(partEtagVO.getEtag(), partEtagVO.getPartNumber()))
                .collect(Collectors.toCollection(ArrayList::new));
        CompleteMultipartUploadRequest request =
                new CompleteMultipartUploadRequest(properties.getBucketName(), objectKey, uploadId, list);
        try {
            obsClient.completeMultipartUpload(request);
        } catch (Exception e) {
            log.error("完成分段上传异常", e);
            throw new ServiceException("完成分段上传异常");
        }
    }

    @Override
    public void cancelMultipartUpload(String objectKey, String uploadId) {
        AbortMultipartUploadRequest request = new AbortMultipartUploadRequest(properties.getBucketName(), objectKey, uploadId);
        try {
            obsClient.abortMultipartUpload(request);
        } catch (Exception e) {
            log.error("取消分段上传任务异常", e);
            throw new ServiceException("取消分段上传任务异常");
        }
    }

    @Override
    public String getFileUrl(String objectKey, Long expires) {
        return this.getFileUrl(objectKey, null, expires);
    }

    @Override
    public String getFileUrl(String objectKey, String fileName, Long expires) {
        if (StringUtil.isBlank(objectKey)) {
            return null;
        }
        // 链接失效时间
        expires = Optional.ofNullable(expires)
                // 参数为空取配置
                .orElse(Optional.ofNullable(properties.getExpires())
                        // 配置为空，默认1小时
                        .orElse(Duration.ofHours(1).getSeconds()));
        TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires);
        request.setBucketName(properties.getBucketName());
        request.setObjectKey(objectKey);
        request.setRequestDate(new Date());
        if (StringUtil.isNotBlank(fileName)) {
            // 文件名不为空，修改响应的文件名
            Map<String, Object> queryParam = new HashMap<>();
            String encodeFileName = encode(fileName);
            queryParam.put(Constants.ObsRequestParams.RESPONSE_CONTENT_DISPOSITION, "attachment;filename*=UTF-8''" + encodeFileName);
            request.setQueryParams(queryParam);
        }
        try {
            TemporarySignatureResponse temporarySignature = obsClient.createTemporarySignature(request);
            return temporarySignature.getSignedUrl();
        } catch (Exception e) {
            log.error("获取文件链接异常", e);
            throw new ServiceException("获取文件链接异常");
        }
    }

    /**
     *  编码文件名
     * @param fileName
     * @return
     */
    private String encode(String fileName) {
        int index = fileName.lastIndexOf(".");
        if (index < 0) {
            return URLEncodeUtil.encode(fileName);
        }
        // 文件名
        String prefix = fileName.substring(0, index);
        // 后缀
        String ext = fileName.substring(index);
        return URLEncodeUtil.encode(prefix) + ext;
    }

    @Override
    public String getUploadPartUrl(String objectKey, String uploadId, Integer partNumber, Long expires) {
        TemporarySignatureRequest request = new TemporarySignatureRequest();
        request.setBucketName(properties.getBucketName());
        request.setObjectKey(objectKey);
        request.setRequestDate(new Date());
        // 链接失效时间
        expires = Optional.ofNullable(expires)
                .orElse(properties.getExpires());
        if (expires != null) {
            request.setExpires(expires);
        }
        request.setMethod(HttpMethodEnum.PUT);
        request.setHeaders(Map.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));
        Map<String, Object> params = new HashMap<>();
        params.put(Constants.ObsRequestParams.PART_NUMBER, String.valueOf(partNumber));
        params.put(Constants.ObsRequestParams.UPLOAD_ID, uploadId);
        request.setQueryParams(params);
        try {
            TemporarySignatureResponse temporarySignature = obsClient.createTemporarySignature(request);
            return temporarySignature.getSignedUrl();
        } catch (Exception e) {
            log.error("获取文件链接异常", e);
            throw new ServiceException("获取文件链接异常");
        }
    }

    @Override
    public List<String> getFileUrls(List<String> objectKeys, Long expires) {
        if (CollectionUtil.isEmpty(objectKeys)) {
            return Collections.emptyList();
        }
        return objectKeys.stream()
                .map(objectKey -> this.getFileUrl(objectKey, expires))
                .toList();
    }

    @Override
    public void downloadFile(String objectKey, String downloadFile) {
        DownloadFileRequest request = new DownloadFileRequest(properties.getBucketName(), objectKey);
        request.setDownloadFile(downloadFile);
        // 开启断点续传模式
        request.setEnableCheckpoint(true);
        Long progressInterval = properties.getDownloadProgressInterval();
        // 设置下载进度日志
        request.setProgressListener(new DownloadProgressListener(objectKey, progressInterval));
        request.setProgressInterval(progressInterval);
        DownloadFileResult result = null;
        try {
            result = obsClient.downloadFile(request);
        } catch (ObsException e) {
            log.error("下载文件异常", e);
            log.error("下载文件异常 objectKey：{} responseCode：{} errorCode：{} errorMessage：{} errorRequestId：{} errorHostId：{}", objectKey, e.getResponseCode(), e.getErrorCode(), e.getErrorMessage(), e.getErrorRequestId(), e.getErrorHostId());
            throw new ServiceException("下载文件异常");
        } catch (Exception e) {
            log.error("下载文件异常", e);
            throw new ServiceException("下载文件异常");
        } finally {
            String etag = Optional.ofNullable(result)
                    .map(DownloadFileResult::getObjectMetadata)
                    .map(ObjectMetadata::getEtag)
                    .orElse(null);
            log.info("下载文件结束 objectKey：{} etag：{}", objectKey, etag);
        }
    }

    /**
     *  上传文件
     * @param objectKey
     * @param file
     * @param is
     * @param expires 过期时间，单位天，过期后自动删除
     */
    private void uploadFile(String objectKey, File file, InputStream is, Integer expires) {
        if (file == null && is == null ) {
            throw new ServiceException("文件不存在");
        }
        PutObjectRequest request = new PutObjectRequest();
        request.setBucketName(properties.getBucketName());
        request.setObjectKey(objectKey);
        if (expires != null) {
            request.setExpires(expires);
        }
        if (is != null) {
            request.setInput(is);
        } else {
            request.setFile(file);
            // 文件不为空，设置md5
            String md5 = getFileMd5(file);
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentMd5(md5);
            request.setMetadata(metadata);
        }
        Long progressInterval = properties.getUploadProgressInterval();
        // 设置上传进度日志
        request.setProgressListener(new UploadProgressListener(objectKey, progressInterval));
        request.setProgressInterval(progressInterval);
        try {
            obsClient.putObject(request);
        } catch (Exception e) {
            log.error("上传文件异常", e);
            throw new ServiceException("上传文件异常");
        }
    }

    @Override
    public String getFileMd5(File file) {
        try {
            return ServiceUtils.toBase64(ServiceUtils.computeMD5Hash(new FileInputStream(file)));
        } catch (NoSuchAlgorithmException | IOException e) {
            log.error("获取文件md5异常", e);
            return null;
        }
    }
}
