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

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.persagy.dmp.common.constant.CommonConstant;
import com.persagy.dmp.common.exception.BusinessException;
import com.persagy.dmp.common.helper.SpringHelper;
import com.persagy.dmp.common.lang.PsDateTime;
import com.persagy.dmp.file.constant.FileCommonConst;
import com.persagy.dmp.file.context.OldFileAppContext;
import com.persagy.dmp.file.dao.FileMapper;
import com.persagy.dmp.file.model.*;
import com.persagy.dmp.file.service.*;
import com.persagy.dmp.file.utils.FileStorageHelper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;

/***
 * Description: 向前兼容旧的文件服务
 * @author : lijie
 * Update By lijie 2021/12/8 15:17
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class CompatibleOldFileServiceImpl implements CompatibleOldFileService {

    private static final String PREFIX = "prefix";
    private static final String MAX_COUNT = "maxCount";
    private static final String FROM = "from";

    private final FileMd5Service fileMd5Service;
    private final IFileService fileService;
    private final FileMapper fileMapper;

    /***
     * Description: 上传文件
     * @param inputStream : 文件流
     * @return : void
     * @author : lijie
     * Update By lijie 2021/12/8 15:20
     */
    @Override
    public void uploadFile(InputStream inputStream) {
        commonUploadFile(inputStream,OldFileAppContext.getContext().getSystemId(),null);
    }

    /***
     * Description: 下载文件
     * @param response : 响应参数
     * @author : lijie
     * Update By lijie 2021/12/8 17:06
     */
    @SneakyThrows
    @Override
    public void getFile(HttpServletResponse response) {
        // 1.必填参数是否存在
        if (StrUtil.isBlank(OldFileAppContext.getContext().getSystemId())
            || StrUtil.isBlank(OldFileAppContext.getContext().getKey())){
            IoUtil.writeUtf8(response.getOutputStream(),Boolean.FALSE,"Incorrect parameters inputted.");
            return;
        }
        commonGetFile(response);
    }

    /***
     * Description: 列出文件key
     * @param param : 请求参数
     * @return : void
     * @author : lijie
     * Update By lijie 2021/12/8 18:22
     */
    @Override
    public List<String> listFiles(JSONObject param) {
        if (StrUtil.isBlank(OldFileAppContext.getContext().getSystemId())){
            return new ArrayList<>();
        }
        // 1.准备分页参数
        Page<FileInfo> page = new Page<>();
        page.setCurrent(0);
        int intValue = param.getIntValue(MAX_COUNT);
        page.setSize(intValue);
        if (intValue<=0||intValue>1000){
            page.setSize(1000);
        }
        if (StrUtil.isNotBlank(param.getString(FROM))){
            // 1.1根据传递的key获得分页开始数
            Integer current = fileMapper.getPageSizeByPrefixAndFrom(param.getString(PREFIX),param.getString(FROM),
                    OldFileAppContext.getContext().getSystemId(),
                    SpringHelper.getInt("persagy.common.sql.limit", 1000));
            if (null!=current){
                page.setCurrent(current);
            }
        }

        List<FileInfo> fileInfos = fileMapper
                .queryListByPrefixAndFrom(page, param.getString(PREFIX), OldFileAppContext.getContext().getSystemId());
        if (CollUtil.isEmpty(fileInfos)){
            return new ArrayList<>();
        }
        return fileInfos.stream().map(FileInfo::getId).collect(Collectors.toList());
    }
    /***
     * Description: 上传工单的文件
     * @param inputStream : 文件流
     * @return : java.lang.String
     * @author : lijie
     * Update By lijie 2021/12/9 10:07
     */
    @Override
    public void uploadWorkOrderFile(InputStream inputStream) {
        commonUploadFile(inputStream,OldFileAppContext.getContext().getProjectId().substring(2),
                OldFileAppContext.getContext().getWorkOrderId());
    }
    /***
     * Description: 下载工单文件
     * @param response : 响应体
     * @author : lijie
     * Update By lijie 2021/12/9 11:06
     */
    @SneakyThrows
    @Override
    public void getWorkOrderFile(HttpServletResponse response) {
        if(StrUtil.isBlank(OldFileAppContext.getContext().getKey())
                || StrUtil.isBlank(OldFileAppContext.getContext().getProjectId())
                || StrUtil.isBlank(OldFileAppContext.getContext().getWorkOrderId())
                || OldFileAppContext.getContext().getProjectId().length()<=2){
            IoUtil.writeUtf8(response.getOutputStream(),Boolean.FALSE,"Incorrect parameters inputted.");
            return;
        }
        commonGetFile(response);
    }
    /***
     * Description: 根据key删除文件
     * @param deleteKeys : 删除key
     * @return : java.util.List<java.lang.String>
     * @author : lijie
     * Update By lijie 2021/12/9 11:31
     */
    @Override
    public Set<String> deleteFileByIds(List<String> deleteKeys) {
        if (CollUtil.isEmpty(deleteKeys)){
            return new HashSet<>();
        }
        // 1.根据要删除的key查询文件信息
        List<FileInfo> fileInfoList = fileMapper.getFileInfoByIds(CollUtil.newHashSet(deleteKeys));
        if (CollUtil.isEmpty(fileInfoList)){
            return new HashSet<>();
        }
        // 2.根据文件md5查询所有关联的文件信息
        List<FileInfo> allMatchMd5FileInfos = fileService.queryByFileMd5s(fileInfoList.stream()
                .filter(fileInfo -> StrUtil.isNotBlank(fileInfo.getFileMd5()))
                .map(FileInfo::getFileMd5)
                .collect(Collectors.toSet()));
        Map<String, Set<String>> fileMd5Map = fileInfoList.stream()
                .filter(fileInfo -> StrUtil.isNotBlank(fileInfo.getFileMd5()))
                .collect(Collectors.groupingBy(FileInfo::getFileMd5, Collectors.mapping(FileInfo::getId, Collectors.toSet())));
        Map<String, Set<String>> allMatchFileMd5Map = allMatchMd5FileInfos.stream()
                .filter(fileInfo -> StrUtil.isNotBlank(fileInfo.getFileMd5()))
                .collect(Collectors.groupingBy(FileInfo::getFileMd5, Collectors.mapping(FileInfo::getId, Collectors.toSet())));
        // 3.处理查询出来的文件信息
        Set<String> deleteFileInfoIds = new HashSet<>();
        Set<String> deleteFileMd5s = new HashSet<>();
        List<FileInfo> deleteFiles = new ArrayList<>();
        for (FileInfo fileInfo : fileInfoList) {
            deleteFileInfoIds.add(fileInfo.getId());
            // 3.1 文件md5为空
            if (StrUtil.isBlank(fileInfo.getFileMd5())){
                continue;
            }
            // 3.2 文件md5不为空但关联的文件未全部删除
            Set<String> currentFileIds = fileMd5Map.getOrDefault(fileInfo.getFileMd5(), new HashSet<>());
            Set<String> allFileIds = allMatchFileMd5Map.getOrDefault(fileInfo.getFileMd5(), new HashSet<>());
            if (CollUtil.isNotEmpty(CollUtil.subtract(allFileIds,currentFileIds))){
                continue;
            }
            deleteFileMd5s.add(fileInfo.getFileMd5());
            // 3.3 文件未完全上传
            if (!FileCommonConst.UPLOAD_SUCCESS.equals(fileInfo.getUploadStatus())){
                continue;
            }
            deleteFiles.add(fileInfo);
        }
        // 4.删除文件信息
        if (CollUtil.isNotEmpty(deleteFileInfoIds)){
            FileInfo fileInfo = new FileInfo();
            fileInfo.setValid(0);
            LambdaQueryWrapper<FileInfo> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.in(FileInfo::getId,deleteFileInfoIds);
            fileMapper.update(fileInfo,queryWrapper);
        }
        // 5.删除文件MD5信息
        if (CollUtil.isNotEmpty(deleteFileMd5s)){
            FileMd5 fileMd5 = new FileMd5();
            fileMd5.setValid(0);
            LambdaQueryWrapper<FileMd5> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.in(FileMd5::getFileMd5,deleteFileMd5s);
            fileMd5Service.update(fileMd5,queryWrapper);
        }
        // 6.根据bucket和文件名批量删除
        Map<String, Set<String>> deleteFileMap = deleteFiles.stream()
                .filter(fileInfo -> StrUtil.isNotBlank(fileInfo.getFileBucket()) && StrUtil.isNotBlank(fileInfo.getFilePath()))
                .collect(Collectors.groupingBy(FileInfo::getFileBucket, Collectors.mapping(FileInfo::getFilePath, Collectors.toSet())));
        IFileStorageService service = FileStorageFactory.getService();
        service.batchDelete(deleteFileMap);
        return deleteFileInfoIds;
    }

    /***
     * Description: 更改文件key
     * @param from : 旧文件key
     * @param to : 新文件key
     * @return : java.lang.Boolean
     * @author : lijie
     * Update By 2021/12/9 17:05
     */
    @Override
    @Transactional(rollbackFor = BusinessException.class)
    public Boolean fileKeyChange(String from, String to) {
        FileInfo oldFileInfo = fileMapper.getFileInfoById(from);
        if (null==oldFileInfo){
            return true;
        }
        FileInfo newFileInfo = BeanUtil.copyProperties(oldFileInfo, FileInfo.class);
        oldFileInfo.setValid(0);
        newFileInfo.setId(to);
        newFileInfo.setFileName(to);
        fileService.updateById(oldFileInfo);
        return fileService.saveOrUpdate(newFileInfo);
    }
    /***
     * Description: 断点续传之注册上传id
     * @return : java.lang.String
     * @author : lijie
     * Update By 2021/12/9 19:39
     */
    @Override
    public String registerMultipartUpload() {
        FileInfo fileInfo = FileInfoCreator
                .of(CommonConstant.DEFAULT_ID, OldFileAppContext.getContext().getKey(),IdWorker.getIdStr(),
                        null,  null);
        try {
            fileInfo.setCreator(FileInfoCreator.formatBucket(OldFileAppContext.getContext().getSystemId()));
            fileInfo.setId(OldFileAppContext.getContext().getKey());
            fileService.saveOrUpdate(fileInfo);
            return fileInfo.getBusinessId();
        }catch (Exception e){
            return null;
        }
    }

    /***
     * Description: 通用下载文件逻辑
     * @param response : 响应体
     * @author : lijie
     * Update By lijie 2021/12/9 11:06
     */
    @SneakyThrows
    private void commonGetFile(HttpServletResponse response){
        // 2.文件是否存在
        IFileStorageService service = FileStorageFactory.getService();
        FileInfo fileInfo = fileMapper.getFileInfoById(OldFileAppContext.getContext().getKey());
        if (null==fileInfo
                || StrUtil.isBlank(fileInfo.getFileBucket())
                || StrUtil.isBlank(fileInfo.getFilePath())
                || !FileCommonConst.UPLOAD_SUCCESS.equals(fileInfo.getUploadStatus())
                || !service.exists(fileInfo.getFileBucket(),fileInfo.getFilePath())){
            IoUtil.writeUtf8(response.getOutputStream(),Boolean.FALSE,"File not existed");
            return;
        }
        // 3.通过文件流的方式下载文件
        InputStream inputStream = service.download(fileInfo.getFileBucket(), fileInfo.getFilePath(), response, fileInfo.getFileName());
        IoUtil.write(response.getOutputStream(),false,IoUtil.readBytes(inputStream));
        // 4.2021年12月22日12:00:21,先申请下载地址,然后通过重定向的方式直接请求文件服务器进行下载
//        String fetchUrl = service.fetchUrl(fileInfo.getFileBucket(), fileInfo.getFilePath());
//        if (StrUtil.isBlank(fetchUrl)){
//            IoUtil.writeUtf8(response.getOutputStream(),Boolean.FALSE,"File not existed");
//            return;
//        }
//        response.sendRedirect(fetchUrl);
    }

    /***
     * Description: 通用的文件上传逻辑
     * @param inputStream : 文件流
     * @return : java.lang.String
     * @author : lijie
     * Update By lijie 2021/12/9 10:17
     */
    @Override
    public void commonUploadFile(InputStream inputStream,String bucketName,String businessId) {
        IFileStorageService service = FileStorageFactory.getService();
        // 1.获得文件大小
        byte[] streamBytes = FileStorageHelper.getStreamBytes(inputStream);
        String fileMd5Str = DigestUtil.md5Hex(streamBytes);
        // 1.根据文件md5值查询数据库
        FileMd5 fileMd5 = fileMd5Service.queryFileMd5ByFileMd5(fileMd5Str);
        // 2.历史已上传但未上传完成
        if (ObjectUtil.isNotNull(fileMd5)
                && FileCommonConst.UPLOAD_PART.equals(fileMd5.getUploadStatus())) {
            // 2.1 补充文件md5值
            fileMd5.setFileBucket(bucketName);
            fileMd5.setUploadStatus(FileCommonConst.UPLOAD_SUCCESS);
            fileMd5.setFileSize((long)streamBytes.length);
            fileMd5.setCreationTime(new PsDateTime());
            fileMd5Service.ensureFilePath(fileMd5,OldFileAppContext.getContext().getKey());
            // 2.2 上传文件
            service.upload(fileMd5.getFileBucket(),fileMd5.getFilePath(), IoUtil.toStream(streamBytes));
            // 2.3 更新文件md5信息
            fileMd5Service.updateById(fileMd5);
            // 2.4 创建文件信息
            fileService.secondTransmission(OldFileAppContext.getContext().getKey(), CommonConstant.DEFAULT_ID,
                    businessId, null, fileMd5.getFileMd5(), OldFileAppContext.getContext().getKey());
            return;
        }
        // 3.历史已上传且已完成上传
        if (ObjectUtil.isNotNull(fileMd5)
                && FileCommonConst.UPLOAD_SUCCESS.equals(fileMd5.getUploadStatus())
                && service.exists(fileMd5.getFileBucket(),fileMd5.getFilePath())){
            // 秒传
            fileService.secondTransmission(OldFileAppContext.getContext().getKey(),CommonConstant.DEFAULT_ID,
                    businessId, null,fileMd5.getFileMd5(),OldFileAppContext.getContext().getKey());
            return;
        }
        // 4.历史已上传且未完成上传
        if (ObjectUtil.isNotNull(fileMd5)
                && FileCommonConst.UPLOAD_SUCCESS.equals(fileMd5.getUploadStatus())){
            // 4.1 上传文件
            service.upload(fileMd5.getFileBucket(),fileMd5.getFilePath(), IoUtil.toStream(streamBytes));
            // 4.2 秒传
            fileService.secondTransmission(OldFileAppContext.getContext().getKey(),CommonConstant.DEFAULT_ID,
                    businessId, null,fileMd5.getFileMd5(),OldFileAppContext.getContext().getKey());
            return;
        }
        // 5.新上传文件
        fileMd5 = FileMd5Creator.of(bucketName, fileMd5Str, FileCommonConst.UPLOAD_SUCCESS);
        fileMd5.setFileSize((long)streamBytes.length);
        fileMd5.setCreationTime(new PsDateTime());
        fileMd5Service.ensureFilePath(fileMd5,OldFileAppContext.getContext().getKey());
        // 5.1 上传文件
        service.upload(fileMd5.getFileBucket(),fileMd5.getFilePath(), IoUtil.toStream(streamBytes));
        // 5.2 保存md5信息
        fileMd5Service.save(fileMd5);
        // 5.3 保存文件信息
        fileService.secondTransmission(OldFileAppContext.getContext().getKey(),CommonConstant.DEFAULT_ID,
                businessId, null,fileMd5.getFileMd5(),OldFileAppContext.getContext().getKey());
    }
}
