package cn.reghao.tnb.oss.store.service;

import cn.reghao.tnb.common.web.ServletUtil;
import cn.reghao.tnb.oss.api.dto.ObjectChannel;
import cn.reghao.tnb.oss.api.rest.UploadFilePart;
import cn.reghao.tnb.oss.api.rest.UploadFileRet;
import cn.reghao.tnb.oss.api.rest.UploadedPart;
import cn.reghao.tnb.oss.store.db.repository.FilePartRepository;
import cn.reghao.tnb.oss.store.db.repository.ObjectRepository;
import cn.reghao.tnb.oss.store.model.po.FilePart;
import cn.reghao.tnb.oss.store.model.po.FileMultipart;
import cn.reghao.tnb.oss.store.model.vo.ObjectProp;
import cn.reghao.tnb.oss.store.model.vo.ObjectResult;
import cn.reghao.tnb.oss.store.util.StringUtil;
import cn.reghao.jutil.jdk.security.DigestUtil;
import cn.reghao.tnb.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.*;
import java.util.stream.Collectors;

/**
 * @author reghao
 * @date 2023-05-19 10:09:01
 */
@Slf4j
@Service
public class ObjectMultipartUploadService {
    private final ObjectRepository objectRepository;
    private final FileStoreService fileStoreService;
    private final ObjectNameService objectNameService;
    private final PutObjectService putObjectService;
    private final FilePartRepository filePartRepository;

    public ObjectMultipartUploadService(ObjectRepository objectRepository, FileStoreService fileStoreService,
                                        ObjectNameService objectNameService, PutObjectService putObjectService,
                                        FilePartRepository filePartRepository) {
        this.objectRepository = objectRepository;
        this.fileStoreService = fileStoreService;
        this.objectNameService = objectNameService;
        this.putObjectService = putObjectService;
        this.filePartRepository = filePartRepository;
    }

    public UploadedPart getUploadedPart(UploadFilePart uploadFilePart, ObjectChannel objectChannel) throws Exception {
        String sha256sum = uploadFilePart.getIdentifier();
        FileMeta fileMeta = objectRepository.getBySha256sum(sha256sum);
        if (fileMeta != null) {
            String filename = uploadFilePart.getFilename();
            String objectName = fileMeta.getObjectName();
            String suffix = StringUtil.getSuffix(objectName);

            ObjectProp objectProp = objectNameService.getObjectProp(objectChannel, suffix);
            ObjectResult objectResult = putObjectService.copyObject(objectProp, filename, fileMeta);

            String objectId = objectResult.getObjectId();
            String objectName1 = objectResult.getObjectName();
            String host = ServletUtil.getHeader("host");
            String url = String.format("//%s/%s", host, objectName1);
            return new UploadedPart(objectId, url);
        }

        UploadedPart uploadedPart = new UploadedPart();
        FileMultipart fileMultipart = filePartRepository.getFileMultipart(sha256sum);
        if (fileMultipart == null) {
            return uploadedPart;
        }

        String contentId = fileMultipart.getContentId();
        List<Integer> list = filePartRepository.getFileParts(contentId).stream()
                .map(FilePart::getChunkNumber)
                .collect(Collectors.toList());
        uploadedPart.setUploaded(list);
        return uploadedPart;
    }

    /**
     * 处理通过 HTTP 请求提交的分片文件
     *
     * @param
     * @return
     * @date 2023-05-19 10:27:01
     */
    @Transactional(rollbackFor = Exception.class)
    public synchronized UploadFileRet putFilePart(InputStream inputStream,
                                                  UploadFilePart uploadFilePart,
                                                  ObjectChannel objectChannel) 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();

        String sha256sum = uploadFilePart.getIdentifier();
        FileMeta fileMeta = objectRepository.getBySha256sum(sha256sum);
        if (fileMeta != null) {
            String objectName = fileMeta.getObjectName();
            String suffix = StringUtil.getSuffix(objectName);

            ObjectProp objectProp = objectNameService.getObjectProp(objectChannel, suffix);
            ObjectResult objectResult = putObjectService.copyObject(objectProp, filename, fileMeta);

            String objectId = objectResult.getObjectId();
            String objectName1 = objectResult.getObjectName();
            String host = ServletUtil.getHeader("host");
            String url = String.format("//%s/%s", host, objectName1);
            return new UploadFileRet(objectId, url);
        }

        List<FilePart> list = new ArrayList<>();
        String contentId;
        FileMultipart fileMultipart = filePartRepository.getFileMultipart(sha256sum);
        if (fileMultipart == null) {
            contentId = UUID.randomUUID().toString().replace("-", "");
            String absolutePath = fileStoreService.genFilePath(contentId, totalSize, "");
            fileStoreService.createSparseFile(absolutePath, totalSize);

            fileMultipart = new FileMultipart(contentId, uploadFilePart, absolutePath);
            filePartRepository.saveFileParts(fileMultipart);
        } else {
            contentId = fileMultipart.getContentId();
            list = filePartRepository.getFileParts(contentId);
        }

        FilePart filePart = new FilePart(contentId, uploadFilePart);
        filePartRepository.saveFilePart(filePart);
        long pos = (chunkNumber-1) * chunkSize;
        String absolutePath = filePartRepository.getFileMultipart(sha256sum).getAbsolutePath();
        fileStoreService.writeToFile(inputStream, absolutePath, pos);
        if (list.size()+1 != totalParts) {
            return new UploadFileRet(sha256sum);
        } else {
            return mergeFileParts(absolutePath, sha256sum, objectChannel, filename);
        }
    }

    private UploadFileRet mergeFileParts(String absolutePath,
                                         String sha256sum,
                                         ObjectChannel objectChannel,
                                         String filename) throws Exception {
        FileInputStream fis = new FileInputStream(absolutePath);
        String sha256sumMerged = DigestUtil.sha256sum(fis);
        if (!sha256sum.equals(sha256sumMerged)) {
            throw new Exception("分片合并文件的 sha256sum 与原文件不一致!");
        }

        log.info("合并的文件 {}", absolutePath);
        String contentId = filePartRepository.getFileMultipart(sha256sum).getContentId();
        String suffix = StringUtil.getSuffix(filename);
        ObjectProp objectProp = objectNameService.getObjectProp(objectChannel, suffix);
        File savedFile = new File(absolutePath);
        ObjectResult objectResult = putObjectService.putObject(objectProp, contentId, savedFile, filename, sha256sum);

        String objectId = objectResult.getObjectId();
        filePartRepository.updateSetUploaded(sha256sum);
        String host = ServletUtil.getHeader("host");
        String url = String.format("//%s/%s", host, objectProp.getObjectName());
        return new UploadFileRet(objectId, url);
    }
}
