package com.lanyuanxiaoyao.service.ai.knowledge.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.crypto.SecureUtil;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse;
import com.lanyuanxiaoyao.service.ai.knowledge.configuration.KnowledgeConfiguration;
import com.lanyuanxiaoyao.service.ai.knowledge.entity.vo.DataFileVO;
import com.lanyuanxiaoyao.service.ai.knowledge.service.DataFileService;
import jakarta.servlet.http.HttpServletResponse;
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 org.eclipse.collections.api.list.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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 log = LoggerFactory.getLogger(DataFileController.class);

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

  public DataFileController(KnowledgeConfiguration knowledgeConfiguration, DataFileService dataFileService) {
    this.knowledgeConfiguration = knowledgeConfiguration;
    this.dataFileService = dataFileService;

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

  @PostMapping("")
  public AmisResponse<FinishResponse> upload(@RequestParam("file") MultipartFile file) throws IOException {
    String filename = file.getOriginalFilename();
    Long id = dataFileService.initialDataFile(filename);
    String url = StrUtil.format("{}/upload/download/{}", knowledgeConfiguration.getDownloadPrefix(), id);
    byte[] bytes = file.getBytes();
    String originMd5 = SecureUtil.md5(new ByteArrayInputStream(bytes));
    File targetFile = new File(StrUtil.format("{}/{}", uploadFolderPath, originMd5));
    if (targetFile.exists()) {
      dataFileService.updateDataFile(id, FileUtil.getAbsolutePath(targetFile), FileUtil.size(targetFile), originMd5, file.getContentType());
      return AmisResponse.responseSuccess(new FinishResponse(id, filename, url, url));
    }
    File cacheFile = new File(StrUtil.format("{}/{}", cacheFolderPath, id));
    cacheFile = FileUtil.writeBytes(bytes, cacheFile);
    String targetMd5 = SecureUtil.md5(cacheFile);
    if (!StrUtil.equals(originMd5, targetMd5)) {
      throw new RuntimeException("文件上传失败，校验不匹配");
    }
    FileUtil.move(cacheFile, targetFile, true);
    dataFileService.updateDataFile(id, FileUtil.getAbsolutePath(targetFile), FileUtil.size(targetFile), targetMd5, file.getContentType());
    return AmisResponse.responseSuccess(new FinishResponse(id, filename, url, url));
  }

  @GetMapping("/download/{id}")
  public void download(@PathVariable Long id, HttpServletResponse response) throws IOException {
    DataFileVO dataFile = dataFileService.downloadFile(id);
    File targetFile = new File(dataFile.getPath());
    response.setHeader("Content-Type", dataFile.getType());
    response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
    response.setHeader("Content-Disposition", StrUtil.format("attachment; filename={}", URLUtil.encodeAll(dataFile.getFilename())));
    IoUtil.copy(new FileInputStream(targetFile), response.getOutputStream());
  }

  @PostMapping("/start")
  public AmisResponse<StartResponse> start(@RequestBody StartRequest 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);
        }
        String absolutePath = FileUtil.getAbsolutePath(targetFile);
        dataFileService.updateDataFile(
            request.uploadId,
            absolutePath,
            FileUtil.size(targetFile),
            SecureUtil.md5(targetFile),
            FileUtil.getMimeType(absolutePath)
        );
        return AmisResponse.responseSuccess(new FinishResponse(
            request.uploadId,
            request.filename,
            request.uploadId.toString(),
            StrUtil.format("{}/upload/download/{}", knowledgeConfiguration.getDownloadPrefix(), request.uploadId)
        ));
      } 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));
    }
  }

  public static final class StartRequest {
    private String name;
    private String filename;

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }

    public String getFilename() {
      return filename;
    }

    public void setFilename(String filename) {
      this.filename = filename;
    }

    @Override
    public String toString() {
      return "StartRequest{" +
             "name='" + name + '\'' +
             ", filename='" + filename + '\'' +
             '}';
    }
  }

  public static final class StartResponse {
    private String uploadId;

    public StartResponse() {
    }

    public StartResponse(String uploadId) {
      this.uploadId = uploadId;
    }

    public String getUploadId() {
      return uploadId;
    }

    public void setUploadId(String uploadId) {
      this.uploadId = uploadId;
    }

    @Override
    public String toString() {
      return "StartResponse{" +
             "uploadId='" + uploadId + '\'' +
             '}';
    }
  }

  public static final class SliceResponse {
    @JsonProperty("eTag")
    private String eTag;

    public SliceResponse() {
    }

    public SliceResponse(String eTag) {
      this.eTag = eTag;
    }

    public String geteTag() {
      return eTag;
    }

    public void seteTag(String eTag) {
      this.eTag = eTag;
    }

    @Override
    public String toString() {
      return "SliceResponse{" +
             "eTag='" + eTag + '\'' +
             '}';
    }
  }

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

    public String getFilename() {
      return filename;
    }

    public void setFilename(String filename) {
      this.filename = filename;
    }

    public Long getUploadId() {
      return uploadId;
    }

    public void setUploadId(Long uploadId) {
      this.uploadId = uploadId;
    }

    public ImmutableList<Part> getPartList() {
      return partList;
    }

    public void setPartList(ImmutableList<Part> partList) {
      this.partList = partList;
    }

    @Override
    public String toString() {
      return "FinishRequest{" +
             "filename='" + filename + '\'' +
             ", uploadId=" + uploadId +
             ", partList=" + partList +
             '}';
    }

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

      public Integer getPartNumber() {
        return partNumber;
      }

      public void setPartNumber(Integer partNumber) {
        this.partNumber = partNumber;
      }

      public String geteTag() {
        return eTag;
      }

      public void seteTag(String eTag) {
        this.eTag = eTag;
      }

      @Override
      public String toString() {
        return "Part{" +
               "partNumber=" + partNumber +
               ", eTag='" + eTag + '\'' +
               '}';
      }
    }
  }

  public static final class FinishResponse {
    private Long id;
    private String filename;
    private String value;
    private String url;

    public FinishResponse() {
    }

    public FinishResponse(Long id, String filename, String value, String url) {
      this.id = id;
      this.filename = filename;
      this.value = value;
      this.url = url;
    }

    public Long getId() {
      return id;
    }

    public void setId(Long id) {
      this.id = id;
    }

    public String getFilename() {
      return filename;
    }

    public void setFilename(String filename) {
      this.filename = filename;
    }

    public String getValue() {
      return value;
    }

    public void setValue(String value) {
      this.value = value;
    }

    public String getUrl() {
      return url;
    }

    public void setUrl(String url) {
      this.url = url;
    }

    @Override
    public String toString() {
      return "FinishResponse{" +
             "id=" + id +
             ", filename='" + filename + '\'' +
             ", value='" + value + '\'' +
             ", url='" + url + '\'' +
             '}';
    }
  }
}
