package com.ruoyi.file;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.Img;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.ruoyi.common.utils.IdUtils;
import lombok.Data;
import lombok.SneakyThrows;
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.hash.HashInfo;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;


public interface FileService {

  /**
   * 生成保留文件名的规则
   */
  String RULE_KEEP_FILENAME = "/{yyyy}/{MM}/{dd}/{id36}/{filename}.{ext}";

  /**
   * 生成随机文件名的规则
   */
  String RULE_RANDOM_FILENAME = "/{yyyy}/{MM}/{dd}/{id36}.{ext}";

  /**
   * 默认前缀
   */
  String DEFAULT_PREFIX = "default";


  /**
   * 默认图片文件名
   */
  String DEFAULT_IMAGE_FILENAME = "a.webp";

  /**
   * 缩略图的扩展名
   */
  String THUMBNAIL_EXT = ".min.webp";

  /**
   * 源文件的扩展名
   */
  String SRC_EXT = ".zip";

  /**
   * webp图片格式扩展名
   */
  String IMAGE_WEBP = "webp";


  ThreadLocal<Param> paramThreadLocal = ThreadLocal.withInitial(() -> new Param());
  ThreadLocal<HashInfo> hashInfoThreadLocal = new ThreadLocal<>();


  /**
   * 获取文件上传完成后计算的md5值
   * @return
   */
  default  String getMd5() {
    return hashInfoThreadLocal.get()==null?null:hashInfoThreadLocal.get().getMd5();
  }
  /**
   * 获取文件上传完成后计算的sha256值
   * @return
   */
  default  String getSha256() {
    return hashInfoThreadLocal.get()==null?null:hashInfoThreadLocal.get().getSha256();
  }

  /**
   * 初始化分片上传
   *
   * @return 分配上传编号
   */
  String multipartUploadInit();

  /**
   * 分片上传
   *
   * @param uploadId
   * @param partNumber
   * @param inputStream
   */
  void multipartUpload(String uploadId, Integer partNumber, InputStream inputStream);


  /**
   * 分片上传完成
   *
   * @param uploadId
   * @return
   */
  String multipartUploadComplete(String uploadId);


  /**
   * 分片上传取消
   *
   * @param uploadId
   */
  void multipartUploadAbort(String uploadId);

  List<Integer> multipartUploadListParts(String uploadId);

  /**
   * 删除
   *
   * @param url
   */
  void delete(String url);

  /**
   * 下载
   *
   * @param url
   * @return
   */
  Downloader download(String url);

  /**
   * 下载文件
   * 1.支持预签名的跳转
   * 2.本地模式的读取下载
   * 3.否则，直接读取下载
   *
   * @param url      url
   * @param exp      过期时间
   * @param request  请求
   * @param response 响应
   */
  void download(String url, Duration exp, HttpServletRequest request, HttpServletResponse response);

  /**
   * 生成预签名URL
   *
   * @param url 原始URL
   * @param exp 过期时间
   * @return
   */
  String generatePresignedUrl(String url, Duration exp);

  /**
   * 在保存方法之前执行
   * 设置平台
   *
   * @param platform 平台
   * @return
   */
  default FileService setPlatform(String platform) {
    paramThreadLocal.get().setPlatform(platform);
    return this;
  }

  /**
   * 设置计算md5
   * @return
   */
  default  FileService setMd5() {
    paramThreadLocal.get().setMd5(true);
    return this;
  }

  /**
   * 设置是否计算md5
   * @param md5
   * @return
   */
  default  FileService setMd5(boolean md5) {
    paramThreadLocal.get().setMd5(md5);
    return this;
  }

  /**
   * 设置计算Sha256
   * @return
   */
  default  FileService setSha256() {
    paramThreadLocal.get().setSha256(true);
    return this;
  }

  /**
   * 设置是否计算Sha256
   * @return
   */
  default  FileService setSha256(boolean sha256) {
    paramThreadLocal.get().setSha256(sha256);
    return this;
  }



  /**
   * 在保存方法之前执行
   * 设置路径前缀
   *
   * @param prefix 前缀
   * @return
   */
  default FileService setPrefix(String prefix) {
    if (StrUtil.isBlank(prefix)) {
      return this;
    }
    if (!prefix.matches("^[\\w\\-]+$")) {
      throw new RuntimeException("路径前缀规则错误");
    }
    paramThreadLocal.get().setPrefix(prefix);
    return this;
  }


  /**
   * 在保存方法之前执行
   * 设置文件名
   * 保存inputstream流需要设置
   *
   * @param filename
   * @return
   */
  default FileService setFilename(String filename) {
    paramThreadLocal.get().setFilename(filename);
    return this;
  }

  /**
   * 在保存方法之前执行
   * 使用RULE_KEEP_FILENAME规则生成保存路径
   * 不执行使用RULE_RANDOM_FILENAME规则生成保存路径
   *
   * @return
   */
  default FileService setKeepFilename() {
    return setKeepFilename(true);
  }

  /**
   * 在保存方法之前执行
   * 使用RULE_KEEP_FILENAME规则生成保存路径
   * 不执行使用RULE_RANDOM_FILENAME规则生成保存路径
   *
   * @return
   */
  default FileService setKeepFilename(Boolean keepFilename) {
    paramThreadLocal.get().setKeepFilename(keepFilename);
    return this;
  }

  /**
   * 在保存方法之前执行
   * 设置保存路径，执行该方法后，setKeepFilename，setFilename,setRule将无效
   *
   * @param uri 保存路径
   * @return
   */
  default FileService setUri(String uri) {
    paramThreadLocal.get().setUri(uri);
    return this;
  }

  /**
   * 在保存方法之前执行
   * 使用rule规则生成保存路径
   *
   * @param rule
   * @return
   */
  default FileService setRule(String rule) {
    paramThreadLocal.get().setRule(rule);
    return this;
  }

  /**
   * 在saveImage方法之前执行
   * 调整图片大小
   *
   * @param maxWidth
   * @param maxHeight
   * @return
   */
  default FileService setSize(int maxWidth, int maxHeight) {
    paramThreadLocal.get().setMaxWidth(maxWidth);
    paramThreadLocal.get().setMaxHeight(maxHeight);
    return this;
  }


  /**
   * 在saveImage方法之前执行
   * 调整图片大小(默认值)
   *
   * @return
   */
  FileService setSize();

  /**
   * 在saveImage方法之前执行
   * 设置水印
   *
   * @param watermark
   * @return
   */
  default FileService setWatermark(BufferedImage watermark) {
    paramThreadLocal.get().setWatermark(watermark);
    return this;
  }

  /**
   * 是否添加默认水印
   *
   * @param watermark
   * @return
   */
  default FileService setWatermark(Boolean watermark) {
    if (watermark) {
      this.setWatermark();
    } else {
      paramThreadLocal.get().setWatermark(null);
    }
    return this;
  }

  /**
   * 在saveImage方法之前执行
   * 设置默认水印
   *
   * @return
   */
  FileService setWatermark();

  /**
   * 在saveImage方法之前执行
   * 设置同时生成缩略图
   *
   * @return
   */
  default FileService setThumbnail() {
    return setThumbnail(true);
  }

  /**
   * 在saveImage方法之前执行
   * 设置是否同时生成缩略图
   *
   * @return
   */
  FileService setThumbnail(boolean thumbnail);

  default FileService setSaveSrc() {
    return setSaveSrc(true);
  }

  default FileService setSaveSrc(boolean saveSrc) {
    paramThreadLocal.get().setSaveSrc(saveSrc);
    return this;
  }

  /**
   * 在saveImage方法之前执行
   * 设置同时生成缩略图,并指定尺寸
   *
   * @param width
   * @param height
   * @return
   */
  default FileService setThumbnail(int width, int height) {
    paramThreadLocal.get().setThWidth(width);
    paramThreadLocal.get().setThHeight(height);
    return this;
  }


  /**
   * 保存文件
   *
   * @param in
   * @return
   */
  String save(InputStream in);

  /**
   * 保存文件
   *
   * @param file
   * @return
   */
  @SneakyThrows
  default String save(MultipartFile file) {
    if (file == null || file.isEmpty()) {
      throw new RuntimeException("文件不能为空");
    }
    return setFilename(file.getOriginalFilename()).save(file.getInputStream());
  }

  /**
   * 保存图片
   *
   * @param in
   * @return
   */
  String saveImage(InputStream in);

  /**
   * 保存图片
   *
   * @param image 图片
   * @return
   */
  default String saveImage(Image image) {
    return saveImage(ImgUtil.toStream(image, "png"));
  }

  /**
   * 保存图片
   *
   * @param file
   * @return
   */
  @SneakyThrows
  default String saveImage(MultipartFile file) {
    if (file == null || file.isEmpty()) {
      throw new RuntimeException("文件不能为空");
    }
    return setFilename(file.getOriginalFilename()).saveImage(file.getInputStream());
  }


  /**
   * 获取类型提取工具
   *
   * @return
   */
  Tika getTika();

  /**
   * 获取文件类型
   *
   * @param in
   * @return
   */
  @SneakyThrows
  default String getContentType(InputStream in) {
    return getTika().detect(in);
  }

  /**
   * 是否是图片文件
   *
   * @param in
   * @return
   */
  @SneakyThrows
  default boolean isImage(InputStream in) {
    return getContentType(in).startsWith("image/");
  }


  default InputStream formatImage(MultipartFile file, int maxWidth, int maxHeight,
                                  BufferedImage watermark) {
    try {
      if (file == null || file.isEmpty()) {
        throw new RuntimeException("上传图片不能为空");
      }
      if (!file.getContentType().startsWith("image/")) {
        throw new RuntimeException("上传的文件不是图片");
      }
      return formatImage(file.getInputStream(), maxWidth, maxHeight, watermark);
    } catch (IOException e) {
      throw new RuntimeException("处理图片错误", e);
    }
  }

  default InputStream formatImage(Image img, int maxWidth, int maxHeight, BufferedImage watermark) {
    if (img == null) {
      throw new RuntimeException("图片不能为空");
    }
    return formatImage(Img.from(img), maxWidth, maxHeight, watermark);
  }

  default InputStream formatImage(InputStream in, int maxWidth, int maxHeight, BufferedImage watermark) {
    return formatImage(Img.from(in), maxWidth, maxHeight, watermark);
  }

  default InputStream formatImage(Img img, int maxWidth, int maxHeight, BufferedImage watermark) {
    try {
      int w = img.getImg().getWidth(null);
      int h = img.getImg().getHeight(null);
      if (maxWidth > 0 && maxHeight > 0) {
        if (w > maxWidth || h > maxHeight) {
          int outWidth = 0;
          int outHeight = 0;
          outHeight = maxWidth * h / w;
          if (outHeight > maxHeight) {
            outHeight = maxHeight;
            outWidth = outHeight * w / h;
          } else {
            outWidth = maxWidth;
          }
          img = img.scale(outWidth, outHeight);
          w = outWidth;
          h = outHeight;
        }
      }


      if (watermark != null) {
        int ww = watermark.getWidth(null);
        int wh = watermark.getHeight(null);
        if (w > ww && h > wh) {
          img = img.pressImage(watermark, 0, 0, 1f);
        }
      }


      ByteArrayOutputStream pout = new ByteArrayOutputStream();

//      img.setTargetImageType(IMAGE_WEBP).write(pout);
      ImageIO.write(ImgUtil.toBufferedImage(img.getImg()), IMAGE_WEBP, pout);

      ByteArrayInputStream pin = new ByteArrayInputStream(pout.toByteArray());
      pout.close();
      pout = null;
      return pin;
    } catch (Exception e) {
      throw new RuntimeException("处理图片错误", e);
    }
  }

  /**
   * 生成存放的URI并保留文件名
   *
   * @param filename 文件名
   * @param prefix   前缀
   * @return
   */
  default String generateURIKeepFilename(String prefix, String filename) {
    return generateURI(prefix, RULE_KEEP_FILENAME, filename);
  }

  /**
   * 生成存放的URI并保留文件名
   *
   * @param filename 文件名
   * @return
   */
  default String generateURIKeepFilename(String filename) {
    return generateURI(DEFAULT_PREFIX, RULE_KEEP_FILENAME, filename);
  }

  /**
   * 生成存放的URI并随机文件名
   *
   * @param filename 文件名
   * @param prefix   前缀
   * @return
   */
  default String generateURIRandomFilename(String prefix, String filename) {
    return generateURI(prefix, RULE_RANDOM_FILENAME, filename);
  }


  /**
   * 生成存放的URI并随机文件名
   *
   * @param filename 文件名
   * @return
   */
  default String generateURIRandomFilename(String filename) {
    return generateURI(DEFAULT_PREFIX, RULE_RANDOM_FILENAME, filename);
  }

  /**
   * <pre>
   * - 根据存放规则和文件名生成存放的URI
   * - 支持
   * 	- {yyyy}/{MM}/{dd}/{HH}/{mm}/{ss} 年月日时分秒
   * 	- {UUID} 32位的唯一标志
   *  - {i} 自增id
   * 	- {id} 当日int类型的唯一id,防止重复建议+年月日路径
   * 	- {id16} 当日int类型的唯一id16进制表示,防止重复建议+年月日路径
   * 	- {id36} 当日int类型的唯一id36进制表示,防止重复建议+年月日路径
   * 	- {filename} 文件基础名称
   * 	- {ext} 扩展名
   * </pre>
   *
   * @param prefix   前缀
   * @param rule     规则
   * @param filename 文件名
   * @return
   */
  default String generateURI(String prefix, String rule, String filename) {
    if (StrUtil.isBlank(prefix)) {
      prefix = DEFAULT_PREFIX;
    }
    LocalDateTime now = LocalDateTime.now();
    if (rule.contains("{yyyy}")) {
      rule = rule.replace("{yyyy}", "" + now.getYear());
    }
    if (rule.contains("{MM}")) {
      rule = rule.replace("{MM}", String.format("%02d", now.getMonthValue()));
    }
    if (rule.contains("{dd}")) {
      rule = rule.replace("{dd}", String.format("%02d", now.getDayOfMonth()));
    }
    if (rule.contains("{HH}")) {
      rule = rule.replace("{HH}", String.format("%02d", now.getHour()));
    }
    if (rule.contains("{mm}")) {
      rule = rule.replace("{mm}", String.format("%02d", now.getMinute()));
    }
    if (rule.contains("{ss}")) {
      rule = rule.replace("{ss}", String.format("%02d", now.getSecond()));
    }
    if (rule.contains("{UUID}")) {
      rule = rule.replace("{UUID}", UUID.fastUUID().toString(true));
    }
    if (rule.contains("{i}")) {
      rule = rule.replace("{i}", IdUtils.nextId(Id.groupName).toString());
    }
    if (rule.contains("{id}")) {
      rule = rule.replace("{id}", Long.toString(id.nextId()));
    }
    if (rule.contains("{id16}")) {
      rule = rule.replace("{id16}", Long.toString(id.nextId(), 16));
    }
    if (rule.contains("{id36}")) {
      rule = rule.replace("{id36}", Long.toString(id.nextId(), 36));
    }
    if (rule.contains("{filename}")) {
      String temp = null;
      if (filename.contains(".")) {
        temp = filename.substring(0, filename.lastIndexOf("."));
      } else {
        temp = filename;
      }
      rule = rule.replace("{filename}", temp);
    }

    if (rule.contains("{ext}")) {
      String temp = null;
      if (filename.contains(".")) {
        temp = filename.substring(filename.lastIndexOf(".") + 1);
      } else {
        temp = "";
      }
      rule = rule.replace("{ext}", temp.toLowerCase());
    }
    return prefix + rule;
  }

  Id id = new Id();

  static class Id {

    private static final String groupName = "file:id";

    private SymmetricCrypto crypto;

    private String today = DateUtil.today();

    public synchronized Long nextId() {
      if (crypto == null || !today.equals(DateUtil.today())) {
        today = DateUtil.today();
        byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(), today.getBytes()).getEncoded();
        crypto = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
      }
      ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
      buffer.putInt(IdUtils.nextDayId(groupName).intValue());
      return Math.abs(ByteBuffer.wrap(crypto.encrypt(buffer.array())).getLong());
    }

  }

  @Data
  public static class Param {
    private String platform;
    private String prefix = FileService.DEFAULT_PREFIX;
    private String filename;
    private boolean keepFilename = false;
    private boolean saveSrc = false;
    private String rule;
    private String uri;
    private int maxWidth = 0;
    private int maxHeight = 0;
    @JsonIgnore
    private BufferedImage watermark;
    private int thWidth = 0;
    private int thHeight = 0;

    private boolean md5 = false;

    private boolean sha256 = false;

  }
}
