package cn.reghao.oss.store.service;

import cn.reghao.oss.storeapi.dto.ObjectChannel;
import cn.reghao.oss.store.db.mapper.FileMetaMapper;
import cn.reghao.oss.store.model.dto.PathUrl;
import cn.reghao.oss.store.model.vo.ObjectProp;
import cn.reghao.oss.store.util.StringUtil;
import cn.reghao.oss.storeapi.rest.UploadFilePart;
import cn.reghao.oss.storeapi.rest.UploadPrepare;
import cn.reghao.oss.storeapi.rest.UploadPrepareRet;
import cn.reghao.oss.storeapi.rest.UploadFileRet;
import cn.reghao.jutil.jdk.security.DigestUtil;
import cn.reghao.oss.store.model.po.FileMeta;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.util.*;

/**
 * @author reghao
 * @date 2023-05-19 10:09:01
 */
@Slf4j
@Service
public class ObjectMultipartUploadService {
    // 20MiB
    private final static long PART_SIZE = 1024*1024*20;
    private final FileMetaMapper fileMetaMapper;
    private final FileStoreService fileStoreService;
    private final Map<String, Set<Long>> map = new HashMap<>();
    private final Map<String, PathUrl> pathMap = new HashMap<>();
    private final ObjectNameService objectNameService;
    private final PutObjectService putObjectService;
    private final StoreLocalService storeLocalService;

    public ObjectMultipartUploadService(FileMetaMapper fileMetaMapper, FileStoreService fileStoreService,
                                        ObjectNameService objectNameService, PutObjectService putObjectService,
                                        StoreLocalService storeLocalService) {
        this.fileMetaMapper = fileMetaMapper;
        this.fileStoreService = fileStoreService;
        this.objectNameService = objectNameService;
        this.putObjectService = putObjectService;
        this.storeLocalService = storeLocalService;
    }

    public synchronized UploadPrepareRet prepareUpload(UploadPrepare uploadPrepare) {
        String sha256sum = uploadPrepare.getSha256sum();
        FileMeta fileMeta = fileMetaMapper.findBySha256sum(sha256sum);
        if (fileMeta == null) {
            return new UploadPrepareRet("uploadId", PART_SIZE, false, null);
        } else {
            String objectName = fileMeta.getObjectName();
            String url = String.format("//%s/%s", storeLocalService.getDomain(), objectName);
            return new UploadPrepareRet("uploadId", PART_SIZE, false, url);
        }
    }

    /**
     * 处理通过 HTTP 请求提交的分片文件
     *
     * @param
     * @return
     * @date 2023-05-19 10:27:01
     */
    @Transactional(rollbackFor = Exception.class)
    public synchronized UploadFileRet putFilePart(InputStream inputStream, UploadFilePart uploadFilePart) throws Exception {
        String filename = uploadFilePart.getFilename();
        long totalSize = uploadFilePart.getTotalSize();
        long chunkSize = uploadFilePart.getChunkSize();
        int currentPartSize = uploadFilePart.getCurrentChunkSize();
        long totalParts = uploadFilePart.getTotalChunks();
        long chunkNumber = uploadFilePart.getChunkNumber();
        log.info("{} -> {}:{}", currentPartSize, totalParts, chunkNumber);

        String sha256sum = uploadFilePart.getIdentifier();
        FileMeta fileMeta = fileMetaMapper.findBySha256sum(sha256sum);
        if (fileMeta != null) {
            String objectName = fileMeta.getObjectName();
            String suffix = StringUtil.getSuffix(objectName);
            ObjectChannel objectChannel = null;
            ObjectProp objectProp = objectNameService.getObjectProp(objectChannel, suffix);
            putObjectService.copyObject(objectProp, filename, fileMeta);

            String url = String.format("//%s/%s", storeLocalService.getDomain(), objectProp);
            return new UploadFileRet(sha256sum, url);
        }

        Set<Long> set = map.computeIfAbsent(sha256sum, k -> new HashSet<>());
        if (set.isEmpty()) {
            String contentId = UUID.randomUUID().toString().replace("-", "");
            String absolutePath = fileStoreService.genFilePath(contentId, totalSize, "");
            fileStoreService.createSparseFile(absolutePath, totalSize);

            PathUrl pathUrl = new PathUrl(contentId, absolutePath);
            pathMap.put(sha256sum, pathUrl);
        }

        if (set.add(chunkNumber)) {
            long pos = (chunkNumber-1) * chunkSize;
            String absolutePath = pathMap.get(sha256sum).getAbsolutePath();
            fileStoreService.writeToFile(inputStream, absolutePath, pos);
        }

        if (set.size() != totalParts) {
            return new UploadFileRet(sha256sum);
        } else {
            String contentId = pathMap.get(sha256sum).getContentId();
            String absolutePath = pathMap.get(sha256sum).getAbsolutePath();
            FileInputStream fis = new FileInputStream(absolutePath);
            String sha256sumMerged = DigestUtil.sha256sum(fis);
            if (!sha256sum.equals(sha256sumMerged)) {
                throw new Exception("分片合并文件的 sha256sum 与原文件不一致!");
            } else {
                log.info("合并的文件 {}", absolutePath);
            }

            int channelId = uploadFilePart.getChannelId();
            String suffix = StringUtil.getSuffix(filename);
            ObjectChannel objectChannel = null;
            ObjectProp objectProp = objectNameService.getObjectProp(objectChannel, suffix);
            File savedFile = new File(absolutePath);
            putObjectService.putObject(objectProp, contentId, savedFile, filename, absolutePath);

            map.remove(sha256sum);
            pathMap.remove(sha256sum);
            String url = String.format("//%s/%s", storeLocalService.getDomain(), objectProp);
            return new UploadFileRet(sha256sum, url);
        }
    }
}
