package org.dromara.system.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
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.itextpdf.text.Element;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import jakarta.servlet.http.HttpServletResponse;
import jodd.util.StringUtil;
import lombok.RequiredArgsConstructor;
import org.apache.poi.xslf.usermodel.*;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.OssService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.oss.core.OssClient;
import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.enumd.AccessPolicyType;
import org.dromara.common.oss.factory.OssFactory;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysOss;
import org.dromara.system.domain.SysTenant;
import org.dromara.system.domain.bo.SysOssBo;
import org.dromara.system.domain.vo.SysOssUploadVo;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.mapper.SysOssMapper;
import org.dromara.system.mapper.SysTenantMapper;
import org.dromara.system.service.ISysOssService;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.*;

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

    private final SysOssMapper baseMapper;
    private final SysTenantMapper tenantMapper;

    @Value("${oss.endpoint}")
    private String endpoint;

    @Value("${oss.bucketName}")
    private String bucketName;

    @Value("${oss.accessKeyId}")
    private String accessKeyId;

    @Value("${oss.secretAccessKey}")
    private String secretAccessKey;

    @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 = StreamUtils.toList(result.getRecords(), this::matchingUrl);
        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.getAopProxy(this).getById(id);
            if (ObjectUtil.isNotNull(vo)) {
                try {
                    list.add(this.matchingUrl(vo));
                } catch (Exception ignored) {
                    // 如果oss异常无法连接则将数据直接返回
                    list.add(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.getAopProxy(this).getById(id);
            if (ObjectUtil.isNotNull(vo)) {
                try {
                    list.add(this.matchingUrl(vo).getUrl());
                } catch (Exception ignored) {
                    // 如果oss异常无法连接则将数据直接返回
                    list.add(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(ObjectUtil.isNotNull(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy());
        lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService());
        lqw.orderByAsc(SysOss::getOssId);
        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.getAopProxy(this).getById(ossId);
        if (ObjectUtil.isNull(sysOss)) {
            throw new ServiceException("文件数据不存在!");
        }
        FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
        OssClient storage = OssFactory.instance(sysOss.getService());
        try (InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) {
            int available = inputStream.available();
            IoUtil.copy(inputStream, response.getOutputStream(), available);
            response.setContentLength(available);
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
    }

    @Override
    public SysOssVo upload(MultipartFile file) {
        String originalFileName = file.getOriginalFilename();
        String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length());
        OssClient storage = OssFactory.instance();
        UploadResult uploadResult = null;

        // 获取当前租户信息
        SysTenantVo sysTenantVo = tenantMapper.selectVoOne(new LambdaQueryWrapper<SysTenant>().eq(SysTenant::getTenantId, TenantHelper.getTenantId()));
        // 水印内容
        if (StringUtils.isNotBlank(sysTenantVo.getWaterMarkText())) {
            if (suffix.equals(".pptx") || suffix.equals("ppt")) {
                // ppt添加水印
                ByteArrayOutputStream outputStream = markPPT(file, sysTenantVo.getWaterMarkText());
                uploadResult = storage.uploadSuffix(outputStream.toByteArray(), suffix, file.getContentType());
            } else if (suffix.equals(".pdf")) {
                // pdf添加水印
                ByteArrayOutputStream outputStream = markPdf(file, sysTenantVo.getWaterMarkText());
                uploadResult = storage.uploadSuffix(outputStream.toByteArray(), suffix, file.getContentType());
            } else {
                try {
                    uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } else {
            try {
                uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


        // 保存文件信息
        return buildResultEntity(originalFileName, suffix, storage.getConfigKey(), uploadResult);
    }

    @Override
    public SysOssVo upload(File file) {
        String originalFileName = file.getName();
        String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length());
        OssClient storage = OssFactory.instance();
        UploadResult uploadResult = storage.uploadSuffix(file, suffix);
        // 保存文件信息
        return buildResultEntity(originalFileName, suffix, storage.getConfigKey(), uploadResult);
    }

    @NotNull
    private SysOssVo buildResultEntity(String originalfileName, String suffix, String configKey, UploadResult uploadResult) {
        SysOss oss = new SysOss();
        oss.setUrl(uploadResult.getUrl());
        oss.setFileSuffix(suffix);
        oss.setFileName(uploadResult.getFilename());
        oss.setOriginalName(originalfileName);
        oss.setService(configKey);
        baseMapper.insert(oss);
        SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class);
        return this.matchingUrl(sysOssVo);
    }

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

    /**
     * pdf上传
     *
     * @param file
     * @return
     */
    @Override
    public SysOssVo uploadPdf(MultipartFile file) {
        String originalFileName = file.getOriginalFilename();
        String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length());
        // 添加水印
        ByteArrayOutputStream outputStream = markPdf(file, "测试水印");
        OssClient storage = OssFactory.instance();
        UploadResult uploadResult;
        uploadResult = storage.uploadSuffix(outputStream.toByteArray(), suffix, file.getContentType());
        // 保存文件信息
        return buildResultEntity(originalFileName, suffix, storage.getConfigKey(), uploadResult);
    }

    /**
     * pdf上传
     *
     * @param file
     * @return
     */
    @Override
    public SysOssVo uploadPPT(MultipartFile file) {
        String originalFileName = file.getOriginalFilename();
        String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length());
        // 添加水印
        ByteArrayOutputStream outputStream = markPPT(file, "测试水印");
        OssClient storage = OssFactory.instance();
        UploadResult uploadResult;
        uploadResult = storage.uploadSuffix(outputStream.toByteArray(), suffix, file.getContentType());
        // 保存文件信息
        return buildResultEntity(originalFileName, suffix, storage.getConfigKey(), uploadResult);
    }

    /**
     * 解析数据图片获取
     *
     * @param ossStr
     * @return
     */
    @Override
    public String generateTempUrl(String ossStr) {
        OssClient storage = OssFactory.instance();
        String path = "pictures";
        String key = String.format("%s/%s", path, ossStr);
        // 生成有效期为一天url的图片
        return storage.getPrivateUrl(key, 24 * 60 * 60);
    }

    /**
     * 文件预览地址获取
     *
     * @param ossId
     * @return
     */
    @Override
    public SysOssUploadVo previewDoc(Long ossId) {

        // 存储文件信息
        SysOss sysOss = baseMapper.selectById(ossId);
        if (ObjectUtil.isNull(sysOss)) {
            throw new ServiceException("文件数据不存在!");
        }
        // Object完整路径，完整路径中不能包含Bucket名称。
        String objectName = sysOss.getFileName();

        DefaultCredentialProvider credentialsProvider = CredentialsProviderFactory.newDefaultCredentialProvider(accessKeyId, secretAccessKey);
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);

        SysOssUploadVo ossUploadVo = new SysOssUploadVo();
        ossUploadVo.setOssId(ossId.toString());
        ossUploadVo.setName(sysOss.getOriginalName());
        try {
            // 设置样式，样式中包含文档预览参数。
            String style = "imm/previewdoc,copy_1";
            // 指定签名URL过期时间为60分钟。
            Date expiration = new Date(new Date().getTime() + 1000 * 60 * 60);
            GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.GET);
            req.setExpiration(expiration);
            req.setProcess(style);
            URL previewDocUrl = ossClient.generatePresignedUrl(req);
            ossUploadVo.setUrl(previewDocUrl.toString());
            System.out.println(previewDocUrl);

        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                + "a serious internal problem while trying to communicate with OSS, "
                + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        return ossUploadVo;
    }

    /**
     * 匹配Url
     *
     * @param oss OSS对象
     * @return oss 匹配Url的OSS对象
     */
    private SysOssVo matchingUrl(SysOssVo oss) {
        OssClient storage = OssFactory.instance(oss.getService());
        // 仅修改桶类型为 private 的URL，临时URL时长为120s
        if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
            oss.setUrl(storage.getPrivateUrl(oss.getFileName(), 120));
        }
        return oss;
    }

    /**
     * 给PDF添加水印
     *
     * @param file             原文件
     * @param waterMarkContent 添加水印的内容
     */
    public static ByteArrayOutputStream markPdf(MultipartFile file, String waterMarkContent) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            // 水印的高和宽
            int waterMarkHeight = 80;

            // 读取PDF文件流
            PdfReader pdfReader = new PdfReader(file.getInputStream());

            //PdfDocument pdfDocument = new PdfDocument();

            // 创建PDF文件的模板，可以对模板的内容修改，重新生成新PDF文件
            PdfStamper pdfStamper = new PdfStamper(pdfReader, outputStream);

            //添加PDF自定义属性
            HashMap<String, String> info = pdfReader.getInfo();
            if (StringUtil.isEmpty(info.get("Title"))) {
                info.put("Title", waterMarkContent);
            }
            pdfStamper.setMoreInfo(info);

            // 设置水印字体
            BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);

            // 设置PDF内容的Graphic State 图形状态
            PdfGState pdfGraPhicState = new PdfGState();
            // 填充透明度
            pdfGraPhicState.setFillOpacity(0.1f);
            // 轮廓不透明度
            pdfGraPhicState.setStrokeOpacity(0.1f);

            // PDF页数
            int pdfPageNum = pdfReader.getNumberOfPages() + 1;

            // PDF文件内容字节
            PdfContentByte pdfContent;

            // PDF页面矩形区域
            Rectangle pageRectangle;

            for (int i = 1; i < pdfPageNum; i++) {
                // 获取当前页面矩形区域
                pageRectangle = pdfReader.getPageSizeWithRotation(i);
                // 获取当前页内容，getOverContent表示之后会在页面内容的上方加水印
                pdfContent = pdfStamper.getOverContent(i);

                // 获取当前页内容，getOverContent表示之后会在页面内容的下方加水印
                // pdfContent = pdfStamper.getUnderContent(i);

                pdfContent.saveState();
                // 设置水印透明度
                pdfContent.setGState(pdfGraPhicState);

                // 开启写入文本
                pdfContent.beginText();
                // 设置字体
                pdfContent.setFontAndSize(baseFont, 80);


                // 添加水印文字并旋转20度角
                pdfContent.showTextAligned(Element.ALIGN_CENTER, waterMarkContent, pageRectangle.getWidth()/2,
                    pageRectangle.getHeight()/2 - waterMarkHeight/2, 20);

                // 在高度和宽度维度每隔waterMarkInterval距离添加一个水印
                /*for (int height = waterMarkHeight; height < pageRectangle.getHeight(); height = height + waterMarkInterval) {
                    for (int width = watermarkWeight; width < pageRectangle.getWidth() + watermarkWeight;
                         width = width + waterMarkInterval) {
                        // 添加水印文字并旋转30度角
                        pdfContent.showTextAligned(Element.ALIGN_LEFT, waterMarkContent, width - watermarkWeight,
                            height - waterMarkHeight, 30);
                    }
                }*/
                // 停止写入文本
                pdfContent.endText();
            }
            pdfStamper.close();
            pdfReader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return outputStream;
    }


    /**
     * 给ppt添加水印
     *
     * @param file
     * @param markStr
     * @return
     */
    public static ByteArrayOutputStream markPPT(MultipartFile file, String markStr) {
        ByteArrayOutputStream ous = new ByteArrayOutputStream();
        try {

            XMLSlideShow ppt = new XMLSlideShow(file.getInputStream());

            int fontSize = 80;
            int height = fontSize;
            int width = fontSize * (markStr.length() + 1);
            for (XSLFSlide slide : ppt.getSlides()) {
                // 在每一页上添加水印的代码 创建文本框对象
                XSLFTextShape textBox = slide.createTextBox();
                textBox.setTextRotation(-20D);
                // 创建文本框
                textBox.setAnchor(new java.awt.Rectangle(260,
                    200, width, height));
                // 创建文本段落
                XSLFTextParagraph paragraph = textBox.addNewTextParagraph();
                // 创建文本运行
                XSLFTextRun run1 = paragraph.addNewTextRun();
                run1.setText(markStr);
                run1.setFontColor(new Color(211, 211, 211));  // 字体颜色
                run1.setFontSize(Double.parseDouble(fontSize + ""));  // 字体大小
                run1.setFontFamily("宋体");   // 字体
                paragraph.addLineBreak();

            }
            ppt.write(ous);
            ppt.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ous;
    }
}
