package com.ruoyi.system.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.constant.CacheNames;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.service.OssService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.BeanCopyUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.oss.core.OssClient;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.AccessPolicyType;
import com.ruoyi.oss.factory.OssFactory;
import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.domain.bo.SysOssBo;
import com.ruoyi.system.domain.vo.SysOssVo;
import com.ruoyi.system.mapper.SysOssMapper;
import com.ruoyi.system.service.FileService;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysOssService;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 文件上传 服务层实现
 *
 * @author Lion Li
 */
@RequiredArgsConstructor
@Service
public class SysOssServiceImpl implements ISysOssService, OssService {

  private static final String UPLOAD = "UPLOAD";
  private final ISysConfigService configService;
  private final FileService fileService;
  private final SysOssMapper baseMapper;
  private static ThreadLocal<Boolean> IGNORE_THREAD_LOCAL = new ThreadLocal<>();
  private static ThreadLocal<Service> SERVICE_THREAD_LOCAL = new ThreadLocal<>();

  @Override
  public void setService(Service service, Runnable handle) {
    SERVICE_THREAD_LOCAL.set(service);
    try {
      handle.run();
    } finally {
      SERVICE_THREAD_LOCAL.remove();
    }
  }

  @Override
  public <T> T setService(Service service, Supplier<T> handle) {
    SERVICE_THREAD_LOCAL.set(service);
    try {
      return handle.get();
    } finally {
      SERVICE_THREAD_LOCAL.remove();
    }
  }

  @Override
  public void ignore(Runnable handle) {
    IGNORE_THREAD_LOCAL.set(true);
    try {
      handle.run();
    } finally {
      IGNORE_THREAD_LOCAL.remove();
    }
  }

  @Override
  public <T> T ignore(Supplier<T> handle) {
    IGNORE_THREAD_LOCAL.set(true);
    try {
      return handle.get();
    } finally {
      IGNORE_THREAD_LOCAL.remove();
    }
  }

  @Override
  public TableDataInfo<SysOssVo> queryPageList(SysOssBo bo, PageQuery pageQuery) {
    LambdaQueryWrapper<SysOss> lqw = buildQueryWrapper(bo);
    Page<SysOssVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
    List<SysOssVo> filterResult = result.getRecords().stream().map(this::url).collect(Collectors.toList());
    result.setRecords(filterResult);
    return TableDataInfo.build(result);
  }

  @Override
  public List<SysOssVo> listByIds(Collection<Long> ossIds) {
    List<SysOssVo> list = new ArrayList<>();
    for (Long id : ossIds) {
      SysOssVo vo = SpringUtils.getBean(ISysOssService.class).getById(id);
      if (ObjectUtil.isNotNull(vo)) {
        list.add(this.url(vo));
      }
    }
    return list;
  }

  @Override
  public String selectUrlByIds(String ossIds) {
    List<String> list = new ArrayList<>();
    for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
      SysOssVo vo = SpringUtils.getBean(ISysOssService.class).getById(id);
      if (ObjectUtil.isNotNull(vo)) {
        list.add(this.url(vo).getUrl());
      }
    }
    return String.join(StringUtils.SEPARATOR, list);
  }

  private LambdaQueryWrapper<SysOss> buildQueryWrapper(SysOssBo bo) {
    Map<String, Object> params = bo.getParams();
    LambdaQueryWrapper<SysOss> lqw = Wrappers.lambdaQuery();
    lqw.like(StringUtils.isNotBlank(bo.getFileName()), SysOss::getFileName, bo.getFileName());
    lqw.like(StringUtils.isNotBlank(bo.getOriginalName()), SysOss::getOriginalName, bo.getOriginalName());
    lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), SysOss::getFileSuffix, bo.getFileSuffix());
    lqw.eq(StringUtils.isNotBlank(bo.getUrl()), SysOss::getUrl, bo.getUrl());
    lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
      SysOss::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
    lqw.eq(StringUtils.isNotBlank(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy());
    lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService());
    return lqw;
  }

  @Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
  @Override
  public SysOssVo getById(Long ossId) {
    return baseMapper.selectVoById(ossId);
  }

  @Override
  public void download(Long ossId, HttpServletResponse response) throws IOException {
    SysOssVo sysOss = SpringUtils.getBean(ISysOssService.class).getById(ossId);
    if (ObjectUtil.isNull(sysOss)) {
      throw new ServiceException("文件数据不存在!");
    }
    FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
    response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
    if (UPLOAD.equals(sysOss.getService())) {
      try (InputStream inputStream = new FileInputStream(fileService.getFile(sysOss.getUrl()))) {
        int available = inputStream.available();
        IoUtil.copy(inputStream, response.getOutputStream());
        response.setContentLength(available);
      } catch (Exception e) {
        throw new ServiceException(e.getMessage());
      }
    } else {
      OssClient storage = OssFactory.instance(sysOss.getService());
      try (InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) {
        int available = inputStream.available();
        IoUtil.copy(inputStream, response.getOutputStream());
        response.setContentLength(available);
      } catch (Exception e) {
        throw new ServiceException(e.getMessage());
      }
    }
  }

  public void download(String url, Service service, HttpServletResponse response) throws IOException {
    FileUtils.setAttachmentResponseHeader(response, FileNameUtil.getName(url));
    response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
    if (UPLOAD.equalsIgnoreCase(service.name())) {
      try (InputStream inputStream = new FileInputStream(fileService.getFile(url))) {
        int available = inputStream.available();
        IoUtil.copy(inputStream, response.getOutputStream());
        response.setContentLength(available);
      } catch (Exception e) {
        throw new ServiceException(e.getMessage());
      }
    } else {
      OssClient storage = OssFactory.instance(service.name());
      try (InputStream inputStream = storage.getObjectContent(url)) {
        int available = inputStream.available();
        IoUtil.copy(inputStream, response.getOutputStream());
        response.setContentLength(available);
      } catch (Exception e) {
        throw new ServiceException(e.getMessage());
      }
    }
  }

  @Override
  public InputStream download(Long ossId) throws IOException {
    SysOssVo sysOss = SpringUtils.getBean(ISysOssService.class).getById(ossId);
    return download(sysOss);
  }

  @Override
  public InputStream download(SysOssVo sysOss) throws IOException {
    if (ObjectUtil.isNull(sysOss)) {
      throw new ServiceException("文件数据不存在!");
    }
    if (UPLOAD.equals(sysOss.getService())) {
      return new FileInputStream(fileService.getFile(sysOss.getUrl()));
    } else {
      OssClient storage = OssFactory.instance(sysOss.getService());
      return storage.getObjectContent(sysOss.getUrl());
    }
  }

  @Override
  public InputStream download(String url, Service service) throws IOException {
    if (UPLOAD.equalsIgnoreCase(service.name())) {
      return new FileInputStream(fileService.getFile(url));
    } else {
      OssClient storage = OssFactory.instance(service.name());
      return storage.getObjectContent(url);
    }
  }


  @Override
  public SysOssVo save(InputStream in, String originalFileName, String contentType, String pre, String rule) {
    String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length());
    SysOss oss = new SysOss();
    oss.setOriginalName(originalFileName);
    oss.setFileSuffix(suffix);
    if (configService.selectOssEnabled()) {
      OssClient storage = SERVICE_THREAD_LOCAL.get() == null ? OssFactory.instance() : OssFactory.instance(SERVICE_THREAD_LOCAL.get().name());
      UploadResult uploadResult;
      try {
        String path = "";
        if (StrUtil.isNotBlank(storage.getProperties().getPrefix())) {
          path += storage.getProperties().getPrefix() + "/";

        }
        if (StrUtil.isBlank(pre)) {
          pre = PRE_DEFAULT;
        }
        path += pre + "/" + generateURI(rule, originalFileName);
        uploadResult = storage.upload(in, path, contentType);
      } catch (Exception e) {
        throw new RuntimeException(e.getMessage(), e);
      }
      // 保存文件信息

      oss.setUrl(uploadResult.getUrl());
      oss.setFileName(uploadResult.getFilename());
      oss.setService(storage.getConfigKey());

    } else {
      try {
        String url = fileService.save(in, originalFileName, pre);
        oss.setUrl(url);
        oss.setFileName(url);
        oss.setService(UPLOAD);
      } catch (Exception e) {
        throw new RuntimeException("保存文件失败", e);
      }
    }
    if (IGNORE_THREAD_LOCAL.get() == null) {
      baseMapper.insert(oss);
    }

    SysOssVo sysOssVo = new SysOssVo();
    BeanCopyUtils.copy(oss, sysOssVo);
    return sysOssVo;
  }

  public SysOssVo uploadImgs(MultipartFile file, String pre) {
    return uploadImgs(file, pre, configService.selectImageMaxWidth(), configService.selectImageMaxHeight(), configService.getWatermark());
  }

  @Override
  public SysOssVo uploadImgs(InputStream inputStream, String pre, int maxWidth, int maxHeight, BufferedImage watermark) {
    String originalFileName = "temp.webp";
    String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length());
    SysOss oss = new SysOss();
    oss.setFileSuffix(suffix);
    oss.setOriginalName(originalFileName);
    if (configService.selectOssEnabled()) {

      OssClient storage = SERVICE_THREAD_LOCAL.get() == null ? OssFactory.instance() : OssFactory.instance(SERVICE_THREAD_LOCAL.get().name());
      UploadResult uploadResult;

      try (
        InputStream in = formatImage(inputStream, maxWidth, maxWidth, watermark)
      ) {
        String path = "";
        if (StrUtil.isNotBlank(storage.getProperties().getPrefix())) {

          path += storage.getProperties().getPrefix() + "/";

        }
        if (StrUtil.isNotBlank(pre)) {
          path += pre + "/";
        }
        path += generateURI("{yyyy}/{MM}/{dd}/{id36}.webp", "a.webp");

        uploadResult = storage.upload(in, path, "image/webp");
      } catch (IOException e) {
        throw new RuntimeException(e.getMessage(), e);
      }
      // 保存文件信息

      oss.setUrl(uploadResult.getUrl());
      oss.setFileName(uploadResult.getFilename());
      oss.setService(storage.getConfigKey());
    } else {
      try (
        InputStream in = formatImage(inputStream, maxWidth, maxWidth, watermark)
      ) {
        String url = fileService.save(in, originalFileName + ".webp", pre);
        oss.setUrl(url);
        oss.setFileName(url);
        oss.setService(UPLOAD);
      } catch (IOException e) {
        throw new RuntimeException(e.getMessage(), e);
      }
    }
    if (IGNORE_THREAD_LOCAL.get() == null) {
      baseMapper.insert(oss);
    }
    SysOssVo sysOssVo = new SysOssVo();
    BeanCopyUtils.copy(oss, sysOssVo);
    return sysOssVo;
  }

  @Override
  public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
    if (isValid) {
      // 做一些业务上的校验,判断是否需要校验
    }
    List<SysOss> list = baseMapper.selectBatchIds(ids);
    for (SysOss sysOss : list) {
      if (UPLOAD.equals(sysOss.getService())) {
        fileService.delete(sysOss.getUrl());
      } else {
        OssClient storage = OssFactory.instance(sysOss.getService());
        storage.delete(sysOss.getUrl());
      }
    }
    return baseMapper.deleteBatchIds(ids) > 0;
  }

  @Override
  public SysOssVo url(SysOssVo oss, int second) {
    if (UPLOAD.equals(oss.getService())) {
      return oss;
    } else {
      oss.setUrl(query(oss.getService(), oss.getUrl(), second));
      return oss;
    }
  }

  private String query(String service, String url, int second) {
    OssClient storage = OssFactory.instance(service);
    // 仅修改桶类型为 private 的URL，临时URL时长为120s
    if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
      String old = url;
      if (url.indexOf("://") > -1) {
        url = url.substring(url.indexOf("/", url.indexOf("//") + 2));
      }
      if (url.startsWith("/")) {
        url = url.substring(1);
      }
      url = url.substring(url.indexOf("/") + 1);

      String queryString = storage.getPrivateUrl(url, second);
      queryString = queryString.substring(queryString.indexOf("?"));
      return old + queryString;
    }
    return url;
  }

  @Override
  public String url(Service service, String url, int second) {
    if (Service.upload.equals(service)) {
      return url;
    } else {
      return query(service.name(), url, second);
    }
  }

}
