package com.eshore.gringotts.web.domain.upload.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.eshore.gringotts.web.configuration.UploadConfiguration;
import com.eshore.gringotts.web.configuration.amis.AmisResponse;
import com.eshore.gringotts.web.domain.upload.service.DataFileService;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.collections.api.list.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

/**
 * 文件上传接口
 *
 * @author lanyuanxiaoyao
 * @date 2024-11-21
 */
@RestController
@RequestMapping("/upload")
public class DataFileController {
    private static final Logger logger = LoggerFactory.getLogger(DataFileController.class);

    private final DataFileService dataFileService;
    private final String uploadFolderPath;
    private final String cacheFolderPath;
    private final String sliceFolderPath;

    public DataFileController(UploadConfiguration uploadConfiguration, DataFileService dataFileService) {
        this.dataFileService = dataFileService;

        this.uploadFolderPath = uploadConfiguration.getUploadPath();
        this.cacheFolderPath = StrUtil.format("{}/cache", uploadFolderPath);
        this.sliceFolderPath = StrUtil.format("{}/slice", uploadFolderPath);
    }

    @PostMapping("/start")
    public AmisResponse<StartResponse> start(@RequestBody StartRequest request) {
        logger.info("Request: {}", request);
        Long id = dataFileService.initialDataFile(request.filename);
        return AmisResponse.responseSuccess(new StartResponse(id.toString()));
    }

    @PostMapping("/slice")
    public AmisResponse<SliceResponse> slice(
            @RequestParam("uploadId")
            Long uploadId,
            @RequestParam("partNumber")
            Integer sequence,
            @RequestParam("partSize")
            Long size,
            @RequestParam("file")
            MultipartFile file
    ) throws IOException {
        byte[] bytes = file.getBytes();
        String md5 = SecureUtil.md5(new ByteArrayInputStream(bytes));
        String targetFilename = StrUtil.format("{}-{}", sequence, md5);
        String targetFilePath = sliceFilePath(uploadId, targetFilename);
        FileUtil.mkParentDirs(targetFilePath);
        FileUtil.writeBytes(bytes, targetFilePath);
        return AmisResponse.responseSuccess(new SliceResponse(targetFilename));
    }

    private String sliceFilePath(Long uploadId, String sliceFilename) {
        return StrUtil.format("{}/{}/{}", sliceFolderPath, uploadId, sliceFilename);
    }

    @PostMapping("finish")
    public AmisResponse<FinishResponse> finish(@RequestBody FinishRequest request) {
        if (request.partList.anySatisfy(part -> !FileUtil.exist(sliceFilePath(request.uploadId, part.eTag)))) {
            throw new RuntimeException("文件校验失败，请重新上传");
        }
        try {
            File cacheFile = new File(StrUtil.format("{}/{}", cacheFolderPath, request.uploadId));
            FileUtil.mkParentDirs(cacheFile);
            if (cacheFile.createNewFile()) {
                try (FileOutputStream fos = new FileOutputStream(cacheFile)) {
                    try (FileChannel fosChannel = fos.getChannel()) {
                        for (FinishRequest.Part part : request.partList) {
                            File sliceFile = new File(sliceFilePath(request.uploadId, part.eTag));
                            try (FileInputStream fis = new FileInputStream(sliceFile)) {
                                try (FileChannel fisChannel = fis.getChannel()) {
                                    fisChannel.transferTo(0, fisChannel.size(), fosChannel);
                                }
                            }
                        }
                    }
                }
                String md5 = SecureUtil.md5(cacheFile);
                File targetFile = new File(StrUtil.format("{}/{}", uploadFolderPath, md5));
                if (!targetFile.exists()) {
                    FileUtil.move(cacheFile, targetFile, true);
                }
                dataFileService.updateDataFile(request.uploadId, FileUtil.getAbsolutePath(targetFile), FileUtil.size(targetFile), SecureUtil.md5(targetFile));
                return AmisResponse.responseSuccess(new FinishResponse(
                        request.uploadId,
                        request.filename,
                        request.uploadId,
                        StrUtil.format("https://localhost:9090")
                ));
            } else {
                throw new RuntimeException("合并文件失败");
            }
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        } finally {
            FileUtil.del(StrUtil.format("{}/{}", cacheFolderPath, request.uploadId));
            FileUtil.del(StrUtil.format("{}/{}", sliceFolderPath, request.uploadId));
        }
    }

    @Data
    public static final class StartRequest {
        private String name;
        private String filename;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static final class StartResponse {
        private String uploadId;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static final class SliceResponse {
        @JsonProperty("eTag")
        private String eTag;
    }

    @Data
    public static final class FinishRequest {
        private String filename;
        private Long uploadId;
        private ImmutableList<Part> partList;

        @Data
        public static final class Part {
            private Integer partNumber;
            @JsonProperty("eTag")
            private String eTag;
        }
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static final class FinishResponse {
        private Long id;
        private String filename;
        private Long value;
        private String url;
    }
}
