package com.persagy.dmp.file.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.persagy.dmp.common.constant.ResponseCode;
import com.persagy.dmp.common.context.AppContext;
import com.persagy.dmp.common.exception.BusinessException;
import com.persagy.dmp.common.lang.PsDate;
import com.persagy.dmp.common.lang.PsDateTime;
import com.persagy.dmp.file.constant.UploadCodeEnum;
import com.persagy.dmp.file.model.*;
import com.persagy.dmp.file.service.FileMd5Service;
import com.persagy.dmp.file.service.IFileService;
import com.persagy.dmp.file.dao.FileMapper;
import com.persagy.dmp.common.constant.ValidEnum;
import com.persagy.dmp.file.constant.FileCommonConst;
import com.persagy.dmp.file.service.FileStorageFactory;
import com.persagy.dmp.file.service.IFileStorageService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 文件服务实现类
 * @author Charlie Yu
 * @date 2021-05-15
 */
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class FileServiceImpl extends ServiceImpl<FileMapper, FileInfo> implements IFileService {

    private final FileMd5Service fileMd5Service;
    private final FileMapper fileMapper;

    @Override
    public FileInfo insertFile(FileInfo vo) {
        //ensureFilePath(vo);
        save(vo);
        return vo;
    }

    @Override
    public FileInfo updateFile(FileInfo vo) {
        FileInfo dbVO = load(vo.getId());
        vo.setCreationTime(dbVO.getCreationTime());
        //ensureFilePath(vo);
        updateById(vo);
        return vo;
    }

    @Override
    public List<FileInfo> batchInsert(List<FileInfo> voList) {
        //voList.forEach(vo -> ensureFilePath(vo));
        saveBatch(voList);
        return voList;
    }

    @Override
    public void deleteFile(FileInfo vo) {
        vo.setValid(ValidEnum.FALSE.getType());
        updateFile(vo);
    }

    @Override
    public FileInfo load(String id) {
        return getById(id);
    }

    @Override
    public List<FileInfo> queryByBusinessId(String businessId) {
        if(StrUtil.isBlank(businessId)) {
            return null;
        }
        LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.
                eq(FileInfo::getBusinessId, businessId).
                eq(FileInfo::getValid, ValidEnum.TRUE.getType());
        return list(queryWrapper);
    }

    /***
     * Description: 初始化文件上传路径列表
     * @param requestData : 请求参数
     * @return : java.util.List<com.persagy.dmp.file.model.FileInfo>
     * @author : lijie
     * @date :2021/10/9 20:27
     * Update By lijie 2021/10/9 20:27
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public UploadMes initChunkUpload(FileRequestData requestData) {
        IFileStorageService service = FileStorageFactory.getService();
        // 1.根据文件md5值查询数据库
        FileMd5 fileMd5 = fileMd5Service.queryFileMd5ByFileMd5(requestData.getFileMd5());
        // 2.历史已上传且已完成上传
        if (ObjectUtil.isNotNull(fileMd5)
                && FileCommonConst.UPLOAD_SUCCESS.equals(fileMd5.getUploadStatus())
                && service.exists(fileMd5.getFileBucket(),fileMd5.getFilePath())){
            // 秒传
            return secondTransmission(requestData.getFileName(),requestData.getGroupCode(),
                    requestData.getBusinessId(),requestData.getExpireDate(),fileMd5.getFileMd5(),requestData.getFileId());
        }
        // 3.历史已上传且已上传分片
        if (ObjectUtil.isNotNull(fileMd5)){
            //获取到该文件已上传分片
            Map<Integer,String> okChunkMap = service
                    .mapChunkObjectNames(service.getChuckBucketName(null), requestData.getFileMd5());
            List<UploadSubMes> chunkUploadUrls = new ArrayList<>();
            if (okChunkMap.size() > 0){
                for (int i = 1; i <= requestData.getChunkCount(); i++) {
                    //判断当前分片是否已经上传过了
                    if(okChunkMap.containsKey(i)){
                        continue;
                    }
                    //生成分片上传url
                    UploadSubMes url = new UploadSubMes();
                    url.setPartNumber(i);
                    url.setUploadUrl(service.createUploadChunkUrl(requestData.getFileBucket(),
                            requestData.getFileMd5(),i));
                    chunkUploadUrls.add(url);
                }
                if (chunkUploadUrls.size() == 0){
                    // 分片已经全部上传,只需要合并文件操作
                    UploadMes uploadMes = new UploadMes();
                    uploadMes.setUploadCode(UploadCodeEnum.CODE_202.getCode());
                    return uploadMes;
                }
                UploadMes uploadMes = new UploadMes();
                uploadMes.setUploadCode(UploadCodeEnum.CODE_203.getCode());
                uploadMes.setContent(chunkUploadUrls);
                return uploadMes;
            }
        }
        // 4.第一次上传或已存在但没有上传过分片的
        return firstInitChunkUploadInternal(requestData);
    }

    /***
     * Description: 合并断点续传的文件
     * @param requestData : 请求参数
     * @return : com.persagy.dmp.file.model.FileInfo
     * @author : lijie
     * @date :2021/10/20 21:57
     * Update By lijie 2021/10/20 21:57
     */
    @Override
    public FileInfo composeFile(FileRequestData requestData) {
        IFileStorageService service = FileStorageFactory.getService();
        // 1.根据md5获取所有分片文件名称(minio的文件名称 = 文件path)
        List<String> chunks = service
                .listObjectNames(service.getChuckBucketName(null),requestData.getFileMd5());
        if (CollUtil.isEmpty(chunks)){
            throw new BusinessException(ResponseCode.A0400.getCode(), "分片文件数量为空");
        }
        // 2.获取数据库里记录的文件信息，修改数据并返回文件信息
        FileMd5 fileMd5 = fileMd5Service.queryFileMd5ByFileMd5(requestData.getFileMd5());
        if (null==fileMd5){
            throw new BusinessException(ResponseCode.A0400.getCode(), "文件信息为空");
        }
        // 3.保存文件信息
        FileInfo fileInfo = FileInfoCreator.of(requestData.getGroupCode(),requestData.getFileName(),
                requestData.getBusinessId(),fileMd5.getFileMd5(),requestData.getExpireDate());
        fileInfo.setCreator(AppContext.getContext().getAccountId());
        // 4.计算文件上传路径
        fileMd5Service.ensureFilePath(fileMd5,fileInfo.getFileName());
        // 5.合并文件
        if(service.composeObject(requestData.getFileBucket(),chunks,fileMd5.getFilePath())){
            fileMd5.setUploadStatus(FileCommonConst.UPLOAD_SUCCESS);
            fileMd5.setFileBucket(requestData.getFileBucket());
            fileMd5.setFileSize(service.getObjectSize(fileMd5.getFileBucket(), fileMd5.getFilePath()));
            fileMd5Service.updateById(fileMd5);
            this.save(fileInfo);
            return fileInfo;
        }
        throw new BusinessException(ResponseCode.B0001.getCode(), "合并文件失败");
    }
    /***
     * Description:
     * @param requestData :
     * @return : com.persagy.dmp.file.domain.UploadMes
     * @author : lijie
     * @date :2021/10/22 19:29
     * Update By lijie 2021/10/22 19:29
     */
    @Override
    public UploadMes initSingleUpload(FileRequestData requestData) {
        IFileStorageService service = FileStorageFactory.getService();
        // 1.根据文件md5值查询数据库
        FileMd5 fileMd5 = fileMd5Service.queryFileMd5ByFileMd5(requestData.getFileMd5());
        // 2.历史已上传但未上传完成
        if (ObjectUtil.isNotNull(fileMd5)
                && FileCommonConst.UPLOAD_PART.equals(fileMd5.getUploadStatus())) {
            // 组装上传信息
            throw new BusinessException(ResponseCode.B0300.getCode(),ResponseCode.B0300.getDesc());
        }
        // 3.历史已上传且已完成上传
        if (ObjectUtil.isNotNull(fileMd5)
                && FileCommonConst.UPLOAD_SUCCESS.equals(fileMd5.getUploadStatus())
                && service.exists(fileMd5.getFileBucket(),fileMd5.getFilePath())){
            // 秒传
            return secondTransmission(requestData.getFileName(),requestData.getGroupCode(),
                    requestData.getBusinessId(),requestData.getExpireDate(),fileMd5.getFileMd5(),requestData.getFileId());
        }
        // 3.历史已上传且已完成上传
        if (ObjectUtil.isNotNull(fileMd5)
                && FileCommonConst.UPLOAD_SUCCESS.equals(fileMd5.getUploadStatus())){
            // 秒传
            UploadMes uploadMes = secondTransmission(requestData.getFileName(), requestData.getGroupCode(),
                    requestData.getBusinessId(), requestData.getExpireDate(), fileMd5.getFileMd5(), requestData.getFileId());
            // 获取上传文件地址
            UploadSubMes uploadSubMes = new UploadSubMes();
            uploadSubMes.setPartNumber(0);
            uploadSubMes.setUploadUrl(service.createUploadUrl(fileMd5.getFileBucket(), fileMd5.getFilePath()));
            uploadMes.setContent(CollUtil.newArrayList(uploadSubMes));
            uploadMes.setUploadCode(UploadCodeEnum.CODE_201.getCode());
            return uploadMes;
        }
        FileInfo fileInfo = FileInfoCreator
                .of(requestData.getGroupCode(), requestData.getFileName(), requestData.getBusinessId(),
                        requestData.getFileMd5(),requestData.getExpireDate());
        fileInfo.setCreator(AppContext.getContext().getAccountId());
        fileInfo.setId(requestData.getFileId());
        fileMd5 = FileMd5Creator.of(requestData.getFileBucket(), requestData.getFileMd5(), FileCommonConst.UPLOAD_SUCCESS);
        fileMd5.setFileSize(requestData.getFileSize());
        fileMd5.setCreationTime(new PsDateTime());
        fileMd5Service.ensureFilePath(fileMd5,requestData.getFileName());
        String uploadUrl = service.createUploadUrl(fileMd5.getFileBucket(), fileMd5.getFilePath());
        this.saveOrUpdate(fileInfo);
        fileMd5Service.save(fileMd5);
        UploadMes uploadMes = new UploadMes();
        uploadMes.setId(fileInfo.getId());
        uploadMes.setUploadCode(UploadCodeEnum.CODE_201.getCode());
        UploadSubMes uploadSubMes = new UploadSubMes();
        uploadSubMes.setPartNumber(0);
        uploadSubMes.setUploadUrl(uploadUrl);
        uploadMes.setContent(CollUtil.newArrayList(uploadSubMes));
        return uploadMes;
    }

    /***
     * Description: 根据Md5查询文件信息
     * @param fileMd5 : 文件md5值
     * @return : java.util.List<com.persagy.dmp.file.model.FileInfo>
     * @author : lijie
     * @date :2021/10/22 22:36
     * Update By lijie 2021/10/22 22:36
     */
    @Override
    public List<FileInfo> queryByFileMd5(String fileMd5) {
        LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(FileInfo::getFileMd5,fileMd5);
        queryWrapper.eq(FileInfo::getValid,true);
        return this.list(queryWrapper);
    }
    /***
     * Description: 初始化文件下载
     * @param requestData : 请求参数
     * @return : com.persagy.dmp.file.model.FileInfo
     * @author : lijie
     * @date :2021/10/23 13:18
     * Update By lijie 2021/10/23 13:18
     */
    @Override
    public FileInfo initFileDownload(FileRequestData requestData) {
        FileInfo fileInfo = fileMapper.getFileInfoById(requestData.getFileId());
        IFileStorageService service = FileStorageFactory.getService();
        if (null==fileInfo || !service.exists(fileInfo.getFileBucket(),fileInfo.getFilePath())){
            throw new BusinessException("文件不存在");
        }
        String fetchUrl = FileStorageFactory.getService().fetchUrl(fileInfo.getFileBucket(), fileInfo.getFilePath());
        fileInfo.setFileDownloadUrl(fetchUrl);
        return fileInfo;
    }

    /***
     * Description: 秒传逻辑
     * @param fileName : 文件名称
     * @param groupCode : 集团编码
     * @param businessId : 业务id
     * @param expireDate : 过期时间
     * @return : com.persagy.dmp.file.domain.FileView
     * @author : lijie
     * @date :2021/10/22 10:03
     * Update By lijie 2021/10/22 10:03
     */
    private UploadMes secondTransmission(String fileName, String groupCode,
                                         String businessId, PsDate expireDate,String fileMd5,String fileId) {
        // 1.生成文件信息并返回
        FileInfo fileInfo = FileInfoCreator.of(groupCode,fileName,businessId,fileMd5,expireDate);
        fileInfo.setCreator(AppContext.getContext().getAccountId());
        if (StrUtil.isNotBlank(fileId)){
            fileInfo.setId(fileId);
        }
        // 2.保存文件信息
        this.saveOrUpdate(fileInfo);
        // 3.组装上传信息
        UploadMes uploadMes = new UploadMes();
        uploadMes.setId(fileInfo.getId());
        uploadMes.setUploadCode(UploadCodeEnum.CODE_200.getCode());
        return uploadMes;
    }

    /***
     * Description: 初次上传和已有文件信息但未上传任何分片的情况下则直接生成所有上传url
     * @param requestData :
     * @return : java.util.List<com.persagy.dmp.file.model.FileInfo>
     * @author : lijie
     * @date :2021/10/9 20:36
     * Update By lijie 2021/10/9 20:36
     */
    private UploadMes firstInitChunkUploadInternal(FileRequestData requestData) {
        IFileStorageService service = FileStorageFactory.getService();
        List<String> uploadUrls = service
                .createUploadChunkUrlList(service.getChuckBucketName(null),
                        requestData.getFileMd5(), requestData.getChunkCount());
        List<UploadSubMes> chunkUploadUrls = new ArrayList<>();
        for (int i = 1; i <= uploadUrls.size(); i++) {
            UploadSubMes fileInfo = new UploadSubMes();
            fileInfo.setPartNumber(i);
            fileInfo.setUploadUrl(service.createUploadChunkUrl(requestData.getFileBucket(),
                    requestData.getFileMd5(),i));
            chunkUploadUrls.add(fileInfo);
        }
        //向数据库中记录该文件的Md5上传信息
        FileMd5 fileMd5 = FileMd5Creator.of(requestData.getFileBucket(),requestData.getFileMd5(),FileCommonConst.UPLOAD_PART);
        fileMd5Service.save(fileMd5);
        UploadMes uploadMes = new UploadMes();
        uploadMes.setUploadCode(UploadCodeEnum.CODE_203.getCode());
        uploadMes.setContent(chunkUploadUrls);
        return uploadMes;
    }

}
