package com.ruoyi.file.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.core.util.ZipUtil;
import com.ruoyi.common.utils.HttpDownloadUtil;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.file.FileService;
import com.ruoyi.file.config.SpringFileStorageProperties;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.tika.Tika;
import org.dromara.x.file.storage.core.Downloader;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.constant.Constant;
import org.dromara.x.file.storage.core.platform.FileStorage;
import org.dromara.x.file.storage.core.platform.LocalPlusFileStorage;

import org.dromara.x.file.storage.core.platform.UpyunUssFileStorage;
import org.dromara.x.file.storage.core.presigned.GeneratePresignedUrlResult;
import org.dromara.x.file.storage.core.tika.TikaFactory;
import org.dromara.x.file.storage.core.upload.FilePartInfo;
import org.dromara.x.file.storage.core.upload.MultipartUploadSupportInfo;
import org.dromara.x.file.storage.core.upload.UploadPretreatment;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.*;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
@RequiredArgsConstructor
public class FileServiceImpl implements FileService {

  private final FileStorageService service;
  private final SpringFileStorageProperties properties;
  private final TikaFactory tikaFactory;
  public static final String CACHE_KEY_PREFIX = "file:multipart:";


  @Override
  public String multipartUploadInit() {
    try {

      UploadPretreatment p = new UploadPretreatment();
      Param param = paramThreadLocal.get();
      initUploadPretreatment(p, param);
      FileStorage fileStorage = service.getFileStorage(p.getPlatform());
      MultipartUploadSupportInfo supportInfo = fileStorage.isSupportMultipartUpload();
      if (!(supportInfo.getIsSupport() && supportInfo.getIsSupportAbort() && supportInfo.getIsSupportListParts())) {
        throw new RuntimeException("不支持分片上传");
      }
      boolean isUpyunUss = fileStorage instanceof UpyunUssFileStorage;
      FileInfo fileInfo = service.initiateMultipartUpload()
        .setPlatform(p.getPlatform())
        .setPath(p.getPath())
        .setSaveFilename(p.getSaveFilename())
        .putMetadata(isUpyunUss, "X-Upyun-Multi-Part-Size", String.valueOf(5 * 1024 * 1024))// 设置 Metadata，不需要可以不写
        .init();

      String uploadId = UUID.fastUUID().toString(true);
      RedisUtils.setCacheObject(CACHE_KEY_PREFIX + uploadId, fileInfo, Duration.ofMinutes(60));
      return uploadId;
    } finally {
      paramThreadLocal.remove();
    }
  }

  @Override
  public void multipartUpload(String uploadId, Integer partNumber, InputStream inputStream) {
    FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);
    if (fileInfo == null) {
      throw new RuntimeException("未初始化分片上传");
    }
    service.uploadPart(fileInfo, partNumber, inputStream).upload();
  }


  @Override
  public String multipartUploadComplete(String uploadId) {
    FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);
    if (fileInfo == null) {
      throw new RuntimeException("未初始化分片上传");
    }
    service.completeMultipartUpload(fileInfo).complete();
    RedisUtils.deleteObject(CACHE_KEY_PREFIX + uploadId);
    return fileInfo.getUrl();
  }

  @Override
  public void multipartUploadAbort(String uploadId) {
    FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);

    if (fileInfo == null) {
      throw new RuntimeException("未初始化分片上传");
    }
    service.abortMultipartUpload(fileInfo).abort();
    RedisUtils.deleteObject(CACHE_KEY_PREFIX + uploadId);
  }

  @Override
  public List<Integer> multipartUploadListParts(String uploadId) {
    FileInfo fileInfo = RedisUtils.getCacheObject(CACHE_KEY_PREFIX + uploadId);
    return service.listParts(fileInfo).listParts().getList().stream().map(FilePartInfo::getPartNumber).collect(Collectors.toList());
  }

  @Override
  public void delete(String url) {
    try {
      FileInfo fileInfo = getFileInfoByURL(url);
      service.delete(fileInfo);
    } finally {
      paramThreadLocal.remove();
    }
  }

  @Override
  public Downloader download(String url) {
    try {
      FileInfo fileInfo = getFileInfoByURL(url);
      return service.download(fileInfo);
    } finally {
      paramThreadLocal.remove();
    }
  }

  @SneakyThrows
  public void download(String url, Duration exp, HttpServletRequest request, HttpServletResponse response) {

    try {

      FileInfo fileInfo = getFileInfoByURL(url);
      FileStorage fileStorage = service.getFileStorage(fileInfo.getPlatform());

      if (fileStorage.isSupportPresignedUrl()) {
        url = generatePresignedUrlInner(url, null, fileInfo, fileStorage);
        log.debug("跳转："+url);
        response.reset();
        response.setStatus(HttpServletResponse.SC_FOUND);
        response.setHeader("Location", url);
        response.getWriter().print("");
        response.flushBuffer();
      } else {
        if (fileStorage instanceof LocalPlusFileStorage) {
          LocalPlusFileStorage fs = (LocalPlusFileStorage) fileStorage;
          File file = new File(new File(fs.getStoragePath(), fileInfo.getPath()), fileInfo.getFilename());
          log.debug("下载本地文件:" + file.getAbsolutePath());
          if (!file.isFile()) {
            throw new FileNotFoundException("文件不存在");
          }
          HttpDownloadUtil.download(request, response, file);

        } else {
          response.reset();
          FileUtils.setAttachmentResponseHeader(response, fileInfo.getFilename());
          service.download(fileInfo).outputStream(response.getOutputStream());
          response.flushBuffer();
        }
      }


    } catch (Exception e) {
      log.warn("下载文件失败", e);
      response.sendError(404, "下载文件失败");
    } finally {
      paramThreadLocal.remove();
    }
  }

  @Override
  @SneakyThrows
  public String generatePresignedUrl(String url, Duration exp) {
    try {
      FileInfo fileInfo = getFileInfoByURL(url);
      FileStorage fileStorage = service.getFileStorage(fileInfo.getPlatform());
      return generatePresignedUrlInner(url, exp, fileInfo, fileStorage);
    } finally {
      paramThreadLocal.remove();
    }
  }

  @SneakyThrows
  private String generatePresignedUrlInner(String url, Duration exp, FileInfo fileInfo, FileStorage fileStorage) {
    if (!fileStorage.isSupportPresignedUrl()) {
      throw new RuntimeException("不支持预签名URL");
    }
    GeneratePresignedUrlResult downloadResult = service.generatePresignedUrl()
      .setPlatform(fileInfo.getPlatform()) // 存储平台，不传使用默认的
      .setPath(fileInfo.getPath()) // 文件路径
      .setFilename(fileInfo.getFilename()) // 文件名，也可以换成缩略图的文件名
      .setMethod(Constant.GeneratePresignedUrl.Method.GET) // 签名方法
      .setExpiration(exp == null ? new Date(System.currentTimeMillis() + 600000) : new Date(System.currentTimeMillis() + exp.toMillis())) // 过期时间 10 分钟
      .putResponseHeaders(
        Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=" + (fileInfo.getFilename().endsWith("." + FileService.IMAGE_WEBP + FileService.SRC_EXT) ? "原图.zip" : fileInfo.getFilename()))

      .generatePresignedUrl();
    log.debug("生成访问预签名 URL 结果：{}", downloadResult);
    String q = downloadResult.getUrl().substring(downloadResult.getUrl().indexOf("?"));
    return URLUtil.encode(url) + q;
  }

  @Override
  public FileService setSize() {
    return setSize(properties.getMaxWidth(), properties.getMaxHeight());
  }

  @Override
  public FileService setWatermark() {
    return setWatermark(properties.getWatermarkImage());
  }

  @Override
  public FileService setThumbnail(boolean thumbnail) {
    if (thumbnail) {
      return setThumbnail(properties.getThWidth(), properties.getThHeight());
    } else {
      return setThumbnail(0, 0);
    }
  }

  @Override
  @SneakyThrows
  public String save(InputStream in) {
    try {
      UploadPretreatment p = service.of(in);
      Param param = paramThreadLocal.get();
      initUploadPretreatment(p, param);
      return p.upload().getUrl();
    } finally {
      paramThreadLocal.remove();
    }
  }


  @Override
  @SneakyThrows
  public String saveImage(InputStream in) {
    try {
      byte[] imageBytes = IoUtil.readBytes(in, true);
      Image image = ImgUtil.read(new ByteArrayInputStream(imageBytes));
      Param param = paramThreadLocal.get();
      param.setKeepFilename(false);
      String srcFilename = param.getFilename();
      param.setFilename(FileService.DEFAULT_IMAGE_FILENAME);
      UploadPretreatment p = service.of(formatImage(image, param.getMaxWidth(), param.getMaxHeight(), param.getWatermark()));
      initUploadPretreatment(p, param);
      String url = p.upload().getUrl();
      if (param.getThHeight() > 0 && param.getThWidth() > 0) {
        UploadPretreatment p1 = service.of(formatImage(image, param.getThWidth(), param.getThHeight(), null));
        String platform = param.getPlatform();
        if (StrUtil.isNotBlank(platform)) {
          p1 = p1.setPlatform(platform);
        }
        p1.setPath(p.getPath());
        p1.setSaveFilename(p.getSaveFilename() + FileService.THUMBNAIL_EXT);
        p1.upload();
      }

      if (param.isSaveSrc() && StrUtil.isNotBlank(srcFilename)) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ZipUtil.zip(out, new String[]{srcFilename}, new InputStream[]{new ByteArrayInputStream(imageBytes)});
        UploadPretreatment p1 = service.of(new ByteArrayInputStream(out.toByteArray()));
        String platform = param.getPlatform();
        if (StrUtil.isNotBlank(platform)) {
          p1 = p1.setPlatform(platform);
        }
        p1.setPath(p.getPath());
        p1.setSaveFilename(p.getSaveFilename() + FileService.SRC_EXT);
        p1.upload();
      }

      return url;
    } finally {
      paramThreadLocal.remove();
    }
  }


  @Override
  public Tika getTika() {
    return tikaFactory.getTika();
  }

  private FileInfo getFileInfoByURL(String url) {
    if (StrUtil.isBlank(url)) {
      throw new RuntimeException("URL不能为空");
    }
    FileInfo fileInfo = new FileInfo();
    String platform = paramThreadLocal.get().getPlatform();
    FileStorage fileStorage = null;
    if (StrUtil.isBlank(platform)) {
      fileStorage = service.getFileStorage();
    } else {
      fileStorage = service.getFileStorage(platform);
    }
    fileInfo.setPlatform(fileStorage.getPlatform());
    String domain = null;
    try {
      domain = (String) BeanUtil.getProperty(fileStorage, "domain");
    } catch (Exception e) {
    }
    if (StrUtil.isBlank(domain)) {
      int index = url.indexOf("://");
      if (index > -1) {
        url = url.substring(url.indexOf("/", index + 4) + 1);
      } else if (url.startsWith("//")) {
        url = url.substring(url.indexOf("/", 3) + 1);
      } else if (url.startsWith("/")) {
        url = url.substring(1);
      }
      index = url.indexOf("/");
      url = url.substring(index + 1);
      index = url.lastIndexOf("/");

      fileInfo.setPath(url.substring(0, index + 1));
      fileInfo.setFilename(url.substring(index + 1));
    } else {
      url = url.substring(domain.length());
      int index = url.lastIndexOf("/");
      fileInfo.setPath(url.substring(0, index + 1));
      fileInfo.setFilename(url.substring(index + 1));
    }


    return fileInfo;
  }

  private void initUploadPretreatment(UploadPretreatment p, Param param) {

    String platform = param.getPlatform();
    if (StrUtil.isNotBlank(platform)) {
      p = p.setPlatform(platform);
    } else {
      p = p.setPlatform(service.getFileStorage().getPlatform());
    }
    String uri = param.getUri();
    if (StrUtil.isBlank(uri)) {
      String rule = param.getRule();
      if (StrUtil.isBlank(rule)) {
        if (param.isKeepFilename()) {
          rule = RULE_KEEP_FILENAME;
        } else {
          rule = RULE_RANDOM_FILENAME;
        }
      }
      uri = generateURI(param.getPrefix(), rule, param.getFilename());
    } else {
      uri = param.getPrefix() + uri;
    }

    int index = uri.lastIndexOf("/") + 1;
    String path = uri.substring(0, index);
    String filename = uri.substring(index);
    log.debug("path={},filename={}", path, filename);
    p.setPath(path).setSaveFilename(filename);
  }
}
