/**
 * BladeX Commercial License Agreement
 * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
 * <p>
 * Use of this software is governed by the Commercial License Agreement
 * obtained after purchasing a license from BladeX.
 * <p>
 * 1. This software is for development use only under a valid license
 * from BladeX.
 * <p>
 * 2. Redistribution of this software's source code to any third party
 * without a commercial license is strictly prohibited.
 * <p>
 * 3. Licensees may copyright their own code but cannot use segments
 * from this software for such purposes. Copyright of this software
 * remains with BladeX.
 * <p>
 * Using this software signifies agreement to this License, and the software
 * must not be used for illegal purposes.
 * <p>
 * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
 * not liable for any claims arising from secondary or illegal development.
 * <p>
 * Author: Chill Zhuang (bladejava@qq.com)
 */
package org.springblade.file.service.impl;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
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.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.file.config.ObsProperties;
import org.springblade.file.convert.FileTaskConvert;
import org.springblade.file.listener.FileUploadedEvent;
import org.springblade.file.mapper.BusinessUnzipTaskMapper;
import org.springblade.file.mapper.FileTaskMapper;
import org.springblade.file.pojo.dto.FileTaskCreateDTO;
import org.springblade.file.pojo.dto.FileTaskUpdateDTO;
import org.springblade.file.pojo.entity.BusinessUnzipTask;
import org.springblade.file.pojo.entity.FileTask;
import org.springblade.file.pojo.entity.FileTaskPart;
import org.springblade.file.pojo.enums.FileTaskStatus;
import org.springblade.file.pojo.vo.FileTaskUpdateVO;
import org.springblade.file.pojo.vo.PartEtagVO;
import org.springblade.file.service.IFileService;
import org.springblade.file.service.IFileTaskPartService;
import org.springblade.file.service.IFileTaskService;
import org.springblade.file.util.ObjectKeyUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 服务实现类
 *
 * @author zhaowei
 */
@Service
@Slf4j
@AllArgsConstructor
public class FileTaskServiceImpl extends ServiceImpl<FileTaskMapper, FileTask> implements IFileTaskService {

    private final FileTaskConvert convert;
    private final IFileTaskPartService partService;
    private final IFileService fileService;
    private final BusinessUnzipTaskMapper unzipTaskMapper;
    private final ObsProperties obsProperties;
    private final ApplicationContext applicationContext;

    @Override
    public FileTaskUpdateVO queryFileTask(Long id, String md5) {
        if (id == null && StringUtil.isBlank(md5)) {
            throw new ServiceException("id和md5不能都为空");
        }
        // 根据md5按创建时间倒序取一条
        FileTask fileTask = this.getOne(Wrappers.<FileTask>lambdaQuery()
                .eq(id != null, FileTask::getId, id)
                .eq(StringUtil.isNotBlank(md5), FileTask::getMd5, md5)
                .orderByDesc(FileTask::getCreateTime)
                .last("limit 1")
        );
        if (fileTask == null) {
            return null;
        }
        return getFileTaskUpdateVO(fileTask);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long createFileTask(FileTaskCreateDTO param) {
        if (!param.isForce()) {
            // 不是强制创建新任务，文件任务存在就报错
            FileTask fileTask = this.getOne(Wrappers.<FileTask>lambdaQuery()
                    .eq(FileTask::getMd5, param.getMd5())
                    .last("limit 1")
            );
            if (Func.notNull(fileTask)) {
                throw new ServiceException("文件任务已存在");
            }
        }

        // 1. 初始化上传任务
        String fileName = param.getAttachmentName();
        String fileSuffix = fileName.substring(fileName.lastIndexOf("."));
        String objectKey = ObjectKeyUtils.generateObjectKey(obsProperties.getZipUploadDir(), fileName);
        String uploadId = fileService.initiateMultipartUpload(objectKey);

        // 2. 保存文件任务
        FileTask addParam = convert.dto2entity(param);
        addParam.setId(IdWorker.getId());
        addParam.setObjectKey(objectKey);
        addParam.setSuffix(fileSuffix);
        // 当前位置
        addParam.setCurrentIndex(0);
        addParam.setUploadId(uploadId);
        // 正在上传
        addParam.setStatus(FileTaskStatus.UPLOADING.getCode());
        this.save(addParam);

        return addParam.getId();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public FileTaskUpdateVO updateFileTask(FileTaskUpdateDTO param) {
        // 校验文件任务
        FileTask fileTask = this.getById(param.getId());
        Func.requireNotNull(fileTask, "文件任务不存在");
        if (FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus())) {
            throw new ServiceException("文件任务已完成");
        }

        // 1. 新增分段
        addPart(param, fileTask);

        // 是否最后一段
        boolean isLast = fileTask.getChunkTotal().equals(param.getPartNumber());
        // 2. 更新文件任务
        boolean update = updateTask(param, isLast);
        // 3. 处理上传结束
        handleFinished(isLast, update, fileTask);
        return this.getUpdateVO(param.getId());
    }

    /**
     *  处理结束
     * @param isLast
     * @param update
     * @param fileTask
     */
    private void handleFinished(boolean isLast, boolean update, FileTask fileTask) {
        if (isLast && update) {
            // 最后一段并且更新成功，合并文件
            List<FileTaskPart> parts = partService.list(Wrappers.<FileTaskPart>lambdaQuery()
                    .eq(FileTaskPart::getFileTaskId, fileTask.getId())
                    .orderByAsc(FileTaskPart::getPartNumber)
            );

            try {
                // 按分段号去重，相同分段号取第一个，如果分段号相同，etag不同，报错
                Map<Integer, FileTaskPart> numberTag = parts.stream()
                        .collect(Collectors.toMap(FileTaskPart::getPartNumber, Function.identity(), (a, b) -> {
                            if (StringUtil.equals(a.getEtag(), b.getEtag())) {
                                return a;
                            }
                            log.error("etag不一致 id1：{} id2：{} etag1：{} etag2：{}", a.getId(), b.getId(), a.getEtag(), b.getEtag());
                            throw new ServiceException("etag不一致");
                        }));
                // 获取要保留的分段id
                List<Long> partId = numberTag.values().stream()
                        .map(FileTaskPart::getId)
                        .toList();
                // 去重
                parts = parts.stream()
                        .filter(e -> partId.contains(e.getId()))
                        .toList();
                List<PartEtagVO> partEtags = parts.stream()
                        .map(convert::entity2vo)
                        .toList();
                // 3. 合并文件
                fileService.completeMultipartUpload(fileTask.getObjectKey(), fileTask.getUploadId(), partEtags);
                // 4. 删除文件任务分段
                List<Long> partIds = parts.stream()
                        .map(FileTaskPart::getId)
                        .toList();
                partService.removeByIds(partIds);
                // 5. 发布上传完成事件
                applicationContext.publishEvent(new FileUploadedEvent(fileTask.getId()));
            } catch (Exception e) {
                log.error("文件分段上传合并文件异常", e);
                log.error("文件任务id：{} objectKey：{}", fileTask.getId(), fileTask.getObjectKey());
                // 更新文件任务状态为失败
                FileTask updateParam = new FileTask();
                updateParam.setId(fileTask.getId());
                updateParam.setStatus(FileTaskStatus.FAILED.getCode());
                this.updateById(updateParam);
            }
        }
    }

    /**
     *  新增分段
     * @param param
     * @param fileTask
     */
    private void addPart(FileTaskUpdateDTO param, FileTask fileTask) {
        // 校验分段
        FileTaskPart part = partService.getOne(Wrappers.<FileTaskPart>lambdaQuery()
                .eq(FileTaskPart::getFileTaskId, fileTask.getId())
                .eq(FileTaskPart::getPartNumber, param.getPartNumber())
                .last("limit 1")
        );
        if (part != null) {
            // 分段存在
            if (StringUtil.equals(part.getEtag(), param.getEtag())) {
                // 且etag和当前的etag相同，不做任何处理
                return;
            }
            // etag和当前的etag不同，报错
            throw new ServiceException("当前分段已存在");
        }

        // 新增分段
        FileTaskPart addParam = new FileTaskPart();
        addParam.setId(IdWorker.getId());
        addParam.setFileTaskId(fileTask.getId());
        addParam.setPartNumber(param.getPartNumber());
        addParam.setEtag(param.getEtag());
        partService.save(addParam);
    }

    @Override
    public String getUploadUrl(Long id, Integer partNumber) {
        FileTask fileTask = this.getById(id);
        Func.requireNotNull(fileTask, "文件任务不存在");
        if (FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus())) {
            throw new ServiceException("文件任务已完成");
        }
        return fileService.getUploadPartUrl(fileTask.getObjectKey(), fileTask.getUploadId(), partNumber, null);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void cancelBatchByIds(List<Long> taskIds) {
        // 查询任务
        List<FileTask> fileTasks = this.listByIds(taskIds);
        if (CollectionUtil.isEmpty(fileTasks)) {
            return;
        }
        // 校验状态
        fileTasks.forEach(task -> {
            if (FileTaskStatus.COMPLETED.getCode().equals(task.getStatus())) {
                throw new ServiceException("文件任务已上传完成");
            }
        });
        // 调用obs取消上传任务
        try {
            for (FileTask fileTask : fileTasks) {
                fileService.cancelMultipartUpload(fileTask.getObjectKey(), fileTask.getUploadId());
            }
        } catch (Exception e) {
            log.error("调用obs取消文件上传任务异常", e);
            throw new ServiceException("调用obs取消文件上传任务异常");
        }
        // 删除上传任务
        this.removeBatchByIds(taskIds);
        // 删除文件任务分片
        List<FileTaskPart> list = partService.list(Wrappers.<FileTaskPart>lambdaQuery()
                .in(FileTaskPart::getFileTaskId, taskIds)
        );
        if (CollectionUtil.isNotEmpty(list)) {
            List<Long> partIds = list.stream()
                    .map(FileTaskPart::getId)
                    .toList();
            partService.removeByIds(partIds);
        }
    }

    @Override
    public FileTaskUpdateVO updateFailed(Long id) {
        // 校验文件任务
        FileTask fileTask = this.getById(id);
        Func.requireNotNull(fileTask, "文件任务不存在");
        if (FileTaskStatus.COMPLETED.getCode().equals(fileTask.getStatus())) {
            throw new ServiceException("文件任务已完成");
        }
        // 更新文件任务状态为失败
        FileTask updateParam = new FileTask();
        updateParam.setId(id);
        updateParam.setStatus(FileTaskStatus.FAILED.getCode());
        this.updateById(updateParam);
        return this.getUpdateVO(id);
    }

    @Override
    public boolean updatePaused(List<Long> ids) {
        if (CollectionUtil.isEmpty(ids)) {
            return false;
        }
        FileTask updateParam = new FileTask();
        // 修改状态为已暂停
        updateParam.setStatus(FileTaskStatus.PAUSED.getCode());
        return this.update(updateParam, Wrappers.<FileTask>lambdaUpdate()
                .in(FileTask::getId, ids)
                // 只更新正在上传状态的
                .eq(FileTask::getStatus, FileTaskStatus.UPLOADING.getCode())
        );
    }

    /**
     *  查询文件任务
     * @param id
     * @return
     */
    private FileTaskUpdateVO getUpdateVO(Long id) {
        FileTask fileTask = this.getById(id);
        return getFileTaskUpdateVO(fileTask);
    }

    /**
     *  查询文件任务
     * @param fileTask
     * @return
     */
    private FileTaskUpdateVO getFileTaskUpdateVO(FileTask fileTask) {
        if (fileTask == null) {
            return null;
        }
        FileTaskUpdateVO updateVO = convert.entity2updateVo(fileTask);
        BusinessUnzipTask businessUnzipTask = unzipTaskMapper.selectOne(Wrappers.<BusinessUnzipTask>lambdaQuery()
                .eq(BusinessUnzipTask::getRelateId, fileTask.getId())
                .last("limit 1")
        );
        if (businessUnzipTask != null && businessUnzipTask.getDocCode() != null) {
            updateVO.setDocCode(businessUnzipTask.getDocCode());
        }
        return updateVO;
    }

    /**
     * 更新任务
     *
     * @param param
     * @param isLast 是否最后一段
     * @return
     */
    private boolean updateTask(FileTaskUpdateDTO param, boolean isLast) {
        FileTask updateParam = new FileTask();
        updateParam.setCurrentIndex(param.getPartNumber());
        if (isLast) {
            // 最后一段，修改任务状态为完成
            updateParam.setStatus(FileTaskStatus.COMPLETED.getCode());
        } else {
            // 非最后一段，修改任务状态为上传中
            updateParam.setStatus(FileTaskStatus.UPLOADING.getCode());
        }
        return this.update(updateParam, Wrappers.<FileTask>lambdaUpdate()
                .eq(FileTask::getId, param.getId())
                .lt(FileTask::getCurrentIndex, param.getPartNumber())
        );
    }
}
