package com.backendsys.modules.upload.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.backendsys.exception.CustException;
import com.backendsys.modules.common.config.security.utils.HttpRequestUtil;
import com.backendsys.modules.sdk.douyincloud.tos.service.DouyinTosService;
import com.backendsys.modules.sdk.tencentcloud.cos.service.TencentCosService;
import com.backendsys.modules.system.entity.SysCommon;
import com.backendsys.modules.system.service.SysCommonService;
import com.backendsys.modules.upload.dao.SysFileDao;
import com.backendsys.modules.upload.entity.SysFile;
import com.backendsys.modules.upload.entity.SysFileResult;
import com.backendsys.modules.upload.enums.StyleEnums;
import com.backendsys.modules.upload.service.SysFileService;
import com.backendsys.modules.upload.utils.UploadUtil;
import com.backendsys.utils.response.PageEntity;
import com.backendsys.utils.response.PageInfoResult;
import com.backendsys.utils.v2.PageUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Service
public class SysFileServiceImpl extends ServiceImpl<SysFileDao, SysFile> implements SysFileService {

    @Autowired
    private HttpRequestUtil httpRequestUtil;

    @Autowired
    private TencentCosService tencentCosService;
    @Autowired
    private DouyinTosService douyinTosService;

    @Autowired
    private UploadUtil uploadUtil;
    @Autowired
    private SysFileDao sysFileDao;
    @Autowired
    private SysCommonService sysCommonService;

    // [方法] 设置缩略图 (参数)
    // 不同的云环境 (target)，缩略图配置也不一样
    //   -1:本地:
    //   1:腾讯云: https://cloud.tencent.com/document/product/436/113295
    //   2:阿里云:
    //   3:抖音云: https://www.volcengine.com/docs/6349/153626
    private SysFile setThumbUrl(SysFile sysFile, Integer width, Integer height, String backgroundColor) {
        if (StrUtil.isEmpty(sysFile.getUpload_id())) {
            // 本地上传
            if (sysFile.getTarget() == -1) {
                sysFile.setUrl_thumb(sysFile.getUrl() + "?w=" + width + "&h=" + height);
            }
            // 腾讯云 (color值通过base64加密, #f8f8f8)
            if (sysFile.getTarget() == 1) {
                System.out.println("base64 encode: " + Base64.encode(backgroundColor));
                sysFile.setUrl_thumb(sysFile.getUrl() + "?imageMogr2/thumbnail/" + width + "x" + height + "/pad/1/color/" + Base64.encode(backgroundColor));
            }
            // 抖音云
            if (sysFile.getTarget() == 3) {
                sysFile.setUrl_thumb(sysFile.getUrl() + "?x-tos-process=image/resize,w_" + width + ",h_" + height + ",m_pad,color_" + backgroundColor);
            }
        }
        return sysFile;
    }

    /**
     * 获取文件列表
     */
    @Override
    public PageEntity selectUploadFileList(SysFile sysFile) {
        PageUtils.startPage();  // 分页
        List<SysFile> sysFileList = sysFileDao.selectUploadFileList(sysFile);

        Integer UPLOAD_THUMB_SIZE = Convert.toInt(sysCommonService.getCommonByTag("UPLOAD_THUMB_SIZE"));
        sysFileList = sysFileList.stream().map(item -> {
            setThumbUrl(item, UPLOAD_THUMB_SIZE, UPLOAD_THUMB_SIZE, StyleEnums.THUMB_BACKGROUND.getValue());
            return item;
        }).collect(Collectors.toList());

        return new PageInfoResult(sysFileList).toEntity();
    }

    @Override
    public Map<String, Object> selectUploadTarget() {
        Integer UPLOAD_TARGET = Convert.toInt(sysCommonService.getCommonByTag("UPLOAD_TARGET"));
        String target_label = StyleEnums.targetToLabel(UPLOAD_TARGET);
        Map<String, Object> resp = new LinkedHashMap<>();
        resp.put("target", UPLOAD_TARGET);
        resp.put("target_label", target_label);
        return resp;
    }

    // [方法] 上传事件
    private SysFile uploadEvent(MultipartFile multipartFile, Long category_id, Integer target) {
        try {
            SysFileResult uploadResult = new SysFileResult();

            // target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)
            if (target == -1) {
                uploadResult = uploadUtil.put(multipartFile);
            }
            if (target == 1) {
                // [腾讯云-上传对象]
                uploadResult = tencentCosService.putObject(multipartFile, null);
            }
            if (target == 3) {
                // [抖音云-上传对象]
                uploadResult = douyinTosService.putObject(multipartFile, null);
            }

            // [新增] 上传文件记录
            SysFile sysFileEntity = new SysFile();
            sysFileEntity.setCategory_id(category_id);
            sysFileEntity.setRequest_id(uploadResult.getRequest_id());
            sysFileEntity.setUser_id(httpRequestUtil.getUserId());
            sysFileEntity.setObject_key(uploadResult.getKey());

            // 文件名
//            sysFileEntity.setName(FileNameUtil.getName(uploadResult.getKey()));

            String filename = multipartFile.getOriginalFilename();
            String filename_prefix = StrUtil.subBefore(filename, '.', true);
            String filename_suffix = StringUtils.getFilenameExtension(filename);
            /*
                filename = 200x134.png
                filename_prefix = 200x134
                filename_suffix = png
             */

            // 查询如果有同名文件，则在文件中加入数量命名
            LambdaQueryWrapper<SysFile> wrapper = new LambdaQueryWrapper<>();
            // 构造正则表达式 (匹配同名、同名(数量))
            String regex = "^" + filename_prefix + "(\\(\\d+\\))?\\." + filename_suffix + "$";
            wrapper.apply("name REGEXP {0}", regex);
            Long count = sysFileDao.selectCount(wrapper);
            if (count > 0) {
                sysFileEntity.setName(filename_prefix + "(" + count + ")." + filename_suffix);
            } else {
                sysFileEntity.setName(filename_prefix + "." + filename_suffix);
            }


            sysFileEntity.setContent_type(multipartFile.getContentType());
            if (target != -1) {
                // 非本地的 (Cos, Tos, ..)
                sysFileEntity.setUrl(uploadResult.getDomain() + "/" + uploadResult.getKey());
            } else {
                // 本地的
                sysFileEntity.setUrl(uploadResult.getDomain());
            }
            sysFileEntity.setSize(multipartFile.getSize());
            if (StrUtil.isNotEmpty(uploadResult.getE_tag())) {
                sysFileEntity.setMd5(uploadResult.getE_tag().replace("\"", ""));
            }
            sysFileEntity.setTarget(target);
            sysFileEntity.setCreate_time(DateUtil.now());
            sysFileEntity.setUpdate_time(DateUtil.now());
            sysFileDao.insert(sysFileEntity);

            return sysFileEntity;

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 上传文件 (单文件大小不超过 n)
     * - target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)
     */
    @Override
    public SysFile uploadSmall(MultipartFile multipartFile, Long category_id) {

        List<SysCommon> sysCommonList = sysCommonService.getCommonByCategory("UPLOAD");

        AtomicReference<Integer> UPLOAD_TARGET = new AtomicReference<>();
        AtomicReference<Long> UPLOAD_MAX_SIZE_MB = new AtomicReference<>();
        AtomicReference<Integer> UPLOAD_THUMB_SIZE = new AtomicReference<>();
        AtomicReference<Boolean> UPLOAD_MD5_DUPLICATE = new AtomicReference<>();

        sysCommonList.stream().forEach(sysCommon -> {
            if (sysCommon.getTag().equals("UPLOAD_TARGET")) UPLOAD_TARGET.set(Convert.toInt(sysCommon.getValue()));
            if (sysCommon.getTag().equals("UPLOAD_MAX_SIZE_MB")) UPLOAD_MAX_SIZE_MB.set(Convert.toLong(sysCommon.getValue()));
            if (sysCommon.getTag().equals("UPLOAD_THUMB_SIZE")) UPLOAD_THUMB_SIZE.set(Convert.toInt(sysCommon.getValue()));
            if (sysCommon.getTag().equals("UPLOAD_MD5_DUPLICATE")) UPLOAD_MD5_DUPLICATE.set(Convert.toBool(sysCommon.getValue()));
        });

        System.out.println("[系统配置] 上传目标(-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云): " + UPLOAD_TARGET);
        System.out.println("[系统配置] 单文件上传大小限制(MB): " + UPLOAD_MAX_SIZE_MB);
        System.out.println("[系统配置] 图片的缩略图宽高尺寸 (px): " + UPLOAD_THUMB_SIZE);
        System.out.println("[系统配置] 是否启用文件MD5查重: " + UPLOAD_MD5_DUPLICATE);

        // 判断文件是否超过大小
        Long MAX_SIZE = UPLOAD_MAX_SIZE_MB.get() * 1024 * 1024;
        if (multipartFile.getSize() > MAX_SIZE) {
            throw new CustException("上传文件不能大于 " + MAX_SIZE / 1024 / 1024 + " MB，请使用大文件上传功能");
        }

        try {

            // [系统配置] 是否启用文件MD5查重
            if (UPLOAD_MD5_DUPLICATE.get()) {
                // - 是
                // 根据文件MD5 判断是否上传过文件
                // - 如果上传过，则仅更新记录，不再上传文件，仅更新文件分类
                // - 如果存在
                String md5 = DigestUtil.md5Hex(multipartFile.getInputStream());

                // 排除文件异常的情况（出现两个以上相同MD5的文件）
                List<SysFile> sysFileEntityList = sysFileDao.selectList(new LambdaQueryWrapper<SysFile>().eq(SysFile::getMd5, md5));
                if (sysFileEntityList != null && sysFileEntityList.size() > 1) {
                    throw new CustException("存在 " + sysFileEntityList.size() + " 个相同Md5 (" + md5 + ") 的文件，请先排重后再上传");
                }

                SysFile sysFileEntity = (sysFileEntityList != null && sysFileEntityList.size() > 0) ? sysFileEntityList.get(0) : null;
                if (sysFileEntity == null) {
                    // [方法] 上传事件
                    sysFileEntity = uploadEvent(multipartFile, category_id, UPLOAD_TARGET.get());
                } else {
                    // [更新] 上传文件记录 (更换文件分类)
                    sysFileEntity.setCategory_id(category_id);
                    sysFileEntity.setUpdate_time(DateUtil.now());
                    sysFileDao.updateById(sysFileEntity);
                }
                sysFileEntity = setThumbUrl(sysFileEntity, UPLOAD_THUMB_SIZE.get(), UPLOAD_THUMB_SIZE.get(), StyleEnums.THUMB_BACKGROUND.getValue());
                return sysFileEntity;

            } else {
                // - 否
                // [方法] 上传事件
                SysFile sysFileEntity = uploadEvent(multipartFile, category_id, UPLOAD_TARGET.get());
                sysFileEntity = setThumbUrl(sysFileEntity, UPLOAD_THUMB_SIZE.get(), UPLOAD_THUMB_SIZE.get(), StyleEnums.THUMB_BACKGROUND.getValue());
                return sysFileEntity;
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }


    // target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)
    private void deleteObject(String object_key, Integer target) {
        if (target == -1) {
            // [本地] 删除文件
            uploadUtil.delete(object_key);
        }
        if (target == 1) {
            // [腾讯云] 删除对象
            tencentCosService.deleteObject(object_key);
            System.out.println("Delete tencent cos object: " + object_key);
        }
        if (target == 3) {
            // [抖音云] 删除对象
            try {
                douyinTosService.deleteObject(object_key);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            System.out.println("Delete douyin tos object: " + object_key);
        }
    }

    /**
     * 删除文件 (包括缩略图，如果有的话)
     */
    @Override
    public Map<String, Object> removeUploadFile(SysFile sysFile, SysFile querySysFile) {

        String object_key = querySysFile.getObject_key();

        // [Delete] 删除文件记录
        sysFileDao.delete(new LambdaQueryWrapper<SysFile>().eq(SysFile::getObject_key, object_key));

        // [异步任务] 创建一个 CompletableFuture 来执行异步任务
        CompletableFuture.runAsync(() -> {
            deleteObject(querySysFile.getObject_key(), querySysFile.getTarget());
        });

        return Map.of("object_key", object_key);
    }

    /**
     * 删除文件 (批量)
     */
    @Override
    public Map<String, Object> removeUploadFileBatch(SysFile sysFile, List<SysFile> querySysFileList) {

        // 判断是否存在
        List<String> object_keys = StrUtil.split(sysFile.getObject_keys(), ',', true, true);

        // [Delete] 批量删除
        sysFileDao.delete(new LambdaQueryWrapper<SysFile>().in(SysFile::getObject_key, object_keys));

        // [异步任务] 创建一个 CompletableFuture 来执行异步任务
        CompletableFuture.runAsync(() -> {
            querySysFileList.stream().forEach(entity -> {
                deleteObject(entity.getObject_key(), entity.getTarget());
            });
        });

        return Map.of("object_keys", object_keys);
    }


    /**
     * 编辑文件 (仅名称)
     */
    @Override
    public Map<String, Object> updateUploadFile(SysFile sysFile) {

        SysFile entity = new SysFile();
        entity.setId(sysFile.getId());
        entity.setName(sysFile.getName());
        sysFileDao.updateById(entity);

        return Map.of("id", sysFile.getId());
    }

}
