package com.cf.imes.module.report.service.template;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bstek.common.exception.ReportDesignException;
import com.bstek.designer.bean.PreviewParameters;
import com.bstek.designer.bean.ReportDefinitionWrapper;
import com.bstek.ureport.definition.ReportDefinition;
import com.bstek.ureport.definition.dataset.ApiDatasetDefinition;
import com.bstek.ureport.definition.dataset.BeanDatasetDefinition;
import com.bstek.ureport.definition.dataset.DatasetDefinition;
import com.bstek.ureport.definition.dataset.SqlDatasetDefinition;
import com.bstek.ureport.definition.datasource.ApiDatasourceDefinition;
import com.bstek.ureport.definition.datasource.DatasourceDefinition;
import com.bstek.ureport.definition.datasource.JdbcDatasourceDefinition;
import com.bstek.ureport.definition.datasource.SpringBeanDatasourceDefinition;
import com.bstek.ureport.definition.searchform.Component;
import com.bstek.ureport.definition.searchform.InputComponent;
import com.bstek.ureport.definition.searchform.SearchForm;
import com.bstek.ureport.definition.searchform.TextInputComponent;
import com.bstek.ureport.exception.ReportComputeException;
import com.bstek.ureport.exception.ReportException;
import com.bstek.ureport.export.ExportUtils;
import com.bstek.ureport.export.ProducerEnum;
import com.bstek.ureport.export.ReportRender;
import com.bstek.ureport.export.html.HtmlProducer;
import com.bstek.ureport.export.html.HtmlReport;
import com.bstek.ureport.model.Report;
import com.bstek.ureport.parser.ReportParser;
import com.bstek.ureport.utils.ToolUtils;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.mybatis.core.generator.SnowFlakeGenerator;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.organ.core.aop.OrganIgnore;
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.report.controller.admin.template.vo.ReportTemplateCommonPreviewReqVO;
import com.cf.imes.module.report.controller.admin.template.vo.ReportTemplatePreviewReqVO;
import com.cf.imes.module.report.controller.admin.template.vo.ReportTemplateReqVO;
import com.cf.imes.module.report.controller.admin.template.vo.ReportTemplateRespVO;
import com.cf.imes.module.report.controller.admin.template.vo.ReportTemplateSaveReqVO;
import com.cf.imes.module.report.dal.dataobject.dataset.ReportDatasetDO;
import com.cf.imes.module.report.dal.dataobject.datasource.ReportDatasourceDO;
import com.cf.imes.module.report.dal.dataobject.template.ReportTemplateDO;
import com.cf.imes.module.report.dal.mysql.dataset.ReportDatasetMapper;
import com.cf.imes.module.report.dal.mysql.datasource.ReportDatasourceMapper;
import com.cf.imes.module.report.dal.mysql.template.ReportTemplateMapper;
import com.cf.imes.module.report.enums.template.ReportTemplateTypeEnum;
import com.cf.imes.module.report.utils.TenantParamsUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;

import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.TEMPLATE_BUILDIN_OPERATION_PERMISSION_ERROR;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.TEMPLATE_IMPORT_CONTENT_EMPTY_ERROR;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.TEMPLATE_NAME_UNIQE_ERROR;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.TEMPLATE_NOT_EXISTS;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.TEMPLATE_PREIVEW_QUERYPARAM_EMPTY;
import static com.cf.imes.module.report.enums.ErrorCodeConstants.TEMPLATE_PREVIEW_TEMPLATE_CONTENT_EMPTY_ERROR;

/**
 * 报表模板信息 Service 实现类
 *
 * @author Gqr
 * @since 2024/7/3 12:00
 */
@Service
@Validated
@Slf4j
public class ReportTemplateServiceImpl implements ReportTemplateService {
    @Resource
    private ReportTemplateMapper templateMapper;

    @Resource
    private ReportDatasourceMapper datasourceMapper;

    @Resource
    private ReportDatasetMapper datasetMapper;

    @Resource
    private SnowFlakeGenerator snowFlakeGenerator;

    private static final String UTF8_CHARSET = CharsetUtil.UTF_8;
    private static final String ATTACHMENT_PREFIX = "attachment; filename=";
    private static final String DATASETIDS_FIELD_NAME = "datasetIds";
    private static final String DATASOURCEIDS_FIELD_NAME = "datasourceIds";
    private static final String DOWNLOAD_SUCCESS_HEADER_CODE = "20000";

    @Resource
    private TenantParamsUtils tenantParamsUtils;

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private ReportTemplateServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ReportTemplateRespVO createReportTemplate(ReportTemplateSaveReqVO createReqVO) {
        boolean isSuperAdmin = SecurityFrameworkUtils.isSuperAdmin();
        // 校验模板名唯一性
        getSelf().validateTemplateNameUnique(createReqVO, isSuperAdmin);
        // url解码xml
        String content = createReqVO.getContent();
        if (StringUtils.isNotEmpty(content)) {
            String decode = URLUtil.decode(content);
            createReqVO.setContent(decode);
            // 解析xml
            new ReportParser().parse(new ByteArrayInputStream(decode.getBytes()), createReqVO.getName());
        }
        // 新增模板
        ReportTemplateDO template = BeanUtils.toBean(createReqVO, ReportTemplateDO.class);
        // 生成id
        template.setId((Long) snowFlakeGenerator.nextId(null));
        // 超管创建的作为内置模板
        if (isSuperAdmin) {
            template.setType(ReportTemplateTypeEnum.SYSTEM);
        } else {
            template.setType(ReportTemplateTypeEnum.CUSTOM);
        }
        templateMapper.insert(template);

        ReportTemplateRespVO reportTemplateRespVO = BeanUtils.toBean(template, ReportTemplateRespVO.class);
        // 查询数据源、数据集
        ReportDefinition reportDefinition = new ReportDefinition();
        queryDatasource(template, reportDefinition);
        reportTemplateRespVO.setDatasource(reportDefinition.getDatasources());
        // 返回实体
        return reportTemplateRespVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long copyReportTemplate(ReportTemplateSaveReqVO createReqVO) {
        boolean isSuperAdmin = SecurityFrameworkUtils.isSuperAdmin();
        Long templateId = createReqVO.getId();
        // 校验存在
        ReportTemplateDO reportTemplateDO = validateTemplateExists(templateId);
        // 校验模板名唯一性
        getSelf().validateTemplateNameUnique(createReqVO, isSuperAdmin);
        ReportTemplateDO copyTemplateDO = BeanUtils.toBean(reportTemplateDO, ReportTemplateDO.class);
        // 重置id
        copyTemplateDO.setId(null);
        copyTemplateDO.setName(createReqVO.getName());
        // 超管创建的作为内置模板
        if (isSuperAdmin) {
            copyTemplateDO.setType(ReportTemplateTypeEnum.SYSTEM);
        } else {
            // 重置所属机构
            copyTemplateDO.setOrganId(SecurityFrameworkUtils.getUserOrganId());
            copyTemplateDO.setType(ReportTemplateTypeEnum.CUSTOM);
        }
        // 重置相关基础信息
        copyTemplateDO.setCreator(null);
        copyTemplateDO.setUpdater(null);
        copyTemplateDO.setUpdateTime(null);
        copyTemplateDO.setCreateTime(null);
        // 解析xml
        String content = copyTemplateDO.getContent();
        if (StringUtils.isNotEmpty(content)) {
            // 解析xml
            new ReportParser().parse(new ByteArrayInputStream(content.getBytes()), createReqVO.getName());
        }
        // 生成id
        copyTemplateDO.setId((Long) snowFlakeGenerator.nextId(null));
        // 新增模版
        templateMapper.insert(copyTemplateDO);
        // 返回主键
        return copyTemplateDO.getId();
    }

    @Override
    public ReportTemplateRespVO updateReportTemplate(ReportTemplateSaveReqVO updateReqVO) {
        Long templateId = updateReqVO.getId();
        // 校验存在
        ReportTemplateDO reportTemplateDO = validateTemplateExists(templateId);
        // 校验模板名唯一性
        getSelf().validateTemplateNameUnique(updateReqVO, SecurityFrameworkUtils.isSuperAdmin());
        // 校验内置模板操作权限
        validateSystemTemplate(reportTemplateDO);
        // url解码xml
        String content = updateReqVO.getContent();
        if (StringUtils.isNotEmpty(content)) {
            String decode = URLUtil.decode(content);
            updateReqVO.setContent(decode);
            // 解析xml
            new ReportParser().parse(new ByteArrayInputStream(decode.getBytes()), updateReqVO.getName());
        }
        // 更新模板
        ReportTemplateDO updateObj = BeanUtils.toBean(updateReqVO, ReportTemplateDO.class);
        templateMapper.updateById(updateObj);

        ReportTemplateRespVO reportTemplateRespVO = BeanUtils.toBean(updateObj, ReportTemplateRespVO.class);
        // 查询数据源、数据集
        ReportDefinition reportDefinition = new ReportDefinition();
        queryDatasource(updateObj, reportDefinition);
        reportTemplateRespVO.setDatasource(reportDefinition.getDatasources());
        // 返回实体
        return reportTemplateRespVO;
    }

    @Override
    public void deleteReportTemplate(Long id) {
        // 校验存在
        ReportTemplateDO reportTemplateDO = validateTemplateExists(id);
        // 校验内置模板操作权限
        validateSystemTemplate(reportTemplateDO);
        // 删除
        templateMapper.physicalDeleteById(id);
    }


    /**
     * 校验只有超管可以操作内置模板
     */
    private void validateSystemTemplate(ReportTemplateDO templateDO) {
        boolean isSuperAdmin = SecurityFrameworkUtils.isSuperAdmin();
        // 非超管不能操作内置模板
        if (ReportTemplateTypeEnum.SYSTEM.equals(templateDO.getType()) && !isSuperAdmin) {
            throw exception(TEMPLATE_BUILDIN_OPERATION_PERMISSION_ERROR);
        }
    }

    /**
     * 校验模板是否存在
     *
     * @param id
     */
    public ReportTemplateDO validateTemplateExists(Long id) {
        ReportTemplateDO reportTemplateDO;
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        if (ObjectUtil.isNull(loginUser)) {
            throw new ServiceException(GlobalErrorCodeConstants.UNAUTHORIZED);
        }
        // 非超管查询内置模板+机构下的
        if (loginUser.getIsSupAdmin()) {
            reportTemplateDO = templateMapper.selectById(id);
        } else {
            reportTemplateDO = templateMapper.selectNormalTemplateById(id, loginUser.getOrganId());
        }
        if (ObjectUtil.isNull(reportTemplateDO)) {
            throw exception(TEMPLATE_NOT_EXISTS);
        } else {
            return reportTemplateDO;
        }
    }

    /**
     * 校验唯一性约束(organId+name)
     *
     * @param reqVO
     */
    @OrganIgnore
    void validateTemplateNameUnique(ReportTemplateSaveReqVO reqVO, boolean isSuperAdmin) {
        Long templateId = reqVO.getId();
        String name = reqVO.getName();
        LambdaQueryWrapper<ReportTemplateDO> wrapper = new LambdaQueryWrapperX<ReportTemplateDO>().eq(ReportTemplateDO::getName, name);
        wrapper.eq(ReportTemplateDO::getOrganId, OrganContextHolder.getOrganId());
        if (ObjectUtil.isNotNull(templateId)) {
            wrapper.ne(ReportTemplateDO::getId, templateId);
        }
        if (isSuperAdmin) {
            wrapper.eq(ReportTemplateDO::getType, ReportTemplateTypeEnum.SYSTEM);
        }
        if (templateMapper.exists(wrapper)) {
            throw exception(TEMPLATE_NAME_UNIQE_ERROR, name);
        }
    }

    @Override
    public ReportTemplateDO getReportTemplate(Long id) {
        // 校验模板
        return validateTemplateExists(id);
    }

    @Override
    public ReportDefinitionWrapper getReportTemplateDefinition(Long id) {
        // 校验模板
        ReportTemplateDO template = validateTemplateExists(id);
        String xmlContent = template.getContent();
        InputStream is;
        if (StringUtils.isNotEmpty(xmlContent)) {
            is = new ByteArrayInputStream(xmlContent.getBytes());
        } else {
            is = this.getClass().getClassLoader().getResourceAsStream("templates/template.ureport.xml");
        }
        // 解析xml
        ReportDefinition reportDefinition = new ReportParser().parse(is, template.getName());
        if (ObjectUtil.isNotNull(template)) {
            queryDatasource(template, reportDefinition);
        }
        ReportDefinitionWrapper reportDefinitionWrapper = new ReportDefinitionWrapper(reportDefinition);
        reportDefinitionWrapper.setDatasetIds(template.getDatasetIds());
        reportDefinitionWrapper.setDatasourceIds(template.getDatasourceIds());
        return reportDefinitionWrapper;
    }

    /**
     * 查询数据源信息（替代ureport xml中的数据源）
     *
     * @param templateDO       模板
     * @param reportDefinition 报表定义
     */
    private void queryDatasource(ReportTemplateDO templateDO, ReportDefinition reportDefinition) {
        Set<Long> datasourceIds = templateDO.getDatasourceIds();
        Set<Long> datasetIds = templateDO.getDatasetIds();
        List<ReportDatasourceDO> reportDatasourceDOS = new ArrayList<>();

        if (CollUtil.isNotEmpty(datasourceIds)) {
            // 查询模板下的数据源
            reportDatasourceDOS = datasourceMapper.selectNormalDatasourceList(datasourceIds, SecurityFrameworkUtils.getUserOrganId());
        }

        List<ReportDatasetDO> reportDatasetDOS = new ArrayList<>();
        if (CollUtil.isNotEmpty(datasetIds)) {
            // 查询模板下的数据集
            reportDatasetDOS = datasetMapper.selectNormalDatasetList(datasetIds);
        }
        // 转换ureport对象后的数据源列表
        List<DatasourceDefinition> datasourceDefinitions = new ArrayList<>();


        // 遍历列表转换为ureport对象
        for (ReportDatasourceDO ds : reportDatasourceDOS) {
            // 转换ureport对象后的数据集列表
            List<DatasetDefinition> datasetDefinitions = new ArrayList<>();
            // 匹配数据源下的数据集
            List<ReportDatasetDO> datasetInDatasourceList = reportDatasetDOS.stream().filter(reportDatasetDO -> ds.getId().equals(reportDatasetDO.getDatasourceId())).toList();
            // 忽略转换的字段名，手动转列表
            String ignorePropertieName = "datasets";
            switch (ds.getType()) {
                case JDBC -> {
                    // 转换对应的ureport对象
                    JdbcDatasourceDefinition jdbcDatasourceDefinition = BeanUtil.copyProperties(ds, JdbcDatasourceDefinition.class, ignorePropertieName);
                    datasetDefinitions.addAll(BeanUtils.toBean(datasetInDatasourceList, SqlDatasetDefinition.class));
                    jdbcDatasourceDefinition.setDatasets(datasetDefinitions);
                    datasourceDefinitions.add(jdbcDatasourceDefinition);
                }
                case SPRING -> {
                    // 转换对应的ureport对象
                    SpringBeanDatasourceDefinition springBeanDatasourceDefinition = BeanUtil.copyProperties(ds, SpringBeanDatasourceDefinition.class, ignorePropertieName);
                    datasetDefinitions.addAll(BeanUtils.toBean(datasetInDatasourceList, BeanDatasetDefinition.class));
                    springBeanDatasourceDefinition.setDatasets(datasetDefinitions);
                    datasourceDefinitions.add(springBeanDatasourceDefinition);
                }
                case API -> {
                    // 转换对应的ureport对象
                    ApiDatasourceDefinition apiDatasourceDefinition = BeanUtil.copyProperties(ds, ApiDatasourceDefinition.class, ignorePropertieName);
                    datasetDefinitions.addAll(BeanUtils.toBean(datasetInDatasourceList, ApiDatasetDefinition.class));
                    apiDatasourceDefinition.setDatasets(datasetDefinitions);
                    datasourceDefinitions.add(apiDatasourceDefinition);
                }
            }
        }
        reportDefinition.setDatasources(datasourceDefinitions);
    }

    @Override
    public List<ReportTemplateDO> getReportTemplateList(ReportTemplateReqVO reqVO) {
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        // 当前登录人机构id
        Long organId = loginUser.getOrganId();
        // 超管查看不同组织模板
        if (ObjectUtil.isNotNull(loginUser) && Boolean.TRUE.equals(loginUser.getIsSupAdmin()) && ObjectUtil.isNotNull(reqVO.getOrganId())) {
            organId = reqVO.getOrganId();
        }
        return templateMapper.selectNormalTemplateList(reqVO.getName(), organId);
    }

    /**
     * 检查查询参数不为空
     *
     * @param queryMap
     */
    private void checkQueryNotEmpty(Map<String, Object> queryMap) {
        if (ObjectUtil.isNotNull(queryMap)) {
            for (Map.Entry<String, Object> entry : queryMap.entrySet()) {
                String key = entry.getKey();
                if (ObjectUtils.isEmpty(entry.getValue()) && ObjectUtil.notEqual(key, DATASETIDS_FIELD_NAME) && ObjectUtil.notEqual(key, DATASOURCEIDS_FIELD_NAME)) {
                    throw ServiceExceptionUtil.exception(TEMPLATE_PREIVEW_QUERYPARAM_EMPTY, key);
                }
            }
        }
    }

    @Override
    public HtmlReport preview(ReportTemplatePreviewReqVO reqVO) {
        // 自定义请求vo转ureport对象
        PreviewParameters params = BeanUtils.toBean(reqVO, PreviewParameters.class);

        HtmlProducer htmlProducer = new HtmlProducer();
        ReportRender reportRender = new ReportRender();
        ReportDefinition reportDefinition = getReportDefinition(reportRender, params, true);

        Map<String, Object> query = params.getQuery();
        // 检查表单配置
        checkSearchForm(reportDefinition.getSearchForm(), query);
        // 增加多租户参数
        tenantParamsUtils.prepareTenantParam(query);
        // 匹配数据源、数据集
        queryPreviewDatasource(query, reportDefinition);

        Report report = reportRender.render(reportDefinition, query);
        return htmlProducer.produce(reportDefinition, report);
    }

    /**
     * 预览查询数据源信息（替代ureport xml中的数据源）
     *
     * @param query            预览参数
     * @param reportDefinition 报表定义
     */
    private void queryPreviewDatasource(Map<String, Object> query, ReportDefinition reportDefinition) {
        Object datasetIdsObj = query.get("datasetIds");
        Object datasourceIdsObj = query.get("datasourceIds");

        List<Long> datasetIds = new ArrayList<>();
        if (ObjectUtil.isNotNull(datasetIdsObj)) {
            datasetIds = (List) datasetIdsObj;
        }

        List<ReportDatasourceDO> reportDatasourceDOS = new ArrayList<>();

        if (ObjectUtil.isNotNull(datasourceIdsObj)) {
            List<Long> datasourceIds = (List) datasourceIdsObj;
            // 查询模板下的数据源
            reportDatasourceDOS = datasourceMapper.selectNormalDatasourceList(new HashSet<>(datasourceIds), SecurityFrameworkUtils.getUserOrganId());
        }

        List<ReportDatasetDO> reportDatasetDOS = new ArrayList<>();
        if (CollUtil.isNotEmpty(datasetIds)) {
            // 查询模板下的数据集
            reportDatasetDOS = datasetMapper.selectNormalDatasetList(new HashSet<>(datasetIds));
        }
        // 转换ureport对象后的数据源列表
        List<DatasourceDefinition> datasourceDefinitions = new ArrayList<>();


        // 遍历列表转换为ureport对象
        for (ReportDatasourceDO ds : reportDatasourceDOS) {
            // 转换ureport对象后的数据集列表
            List<DatasetDefinition> datasetDefinitions = new ArrayList<>();
            // 匹配数据源下的数据集
            List<ReportDatasetDO> datasetInDatasourceList = reportDatasetDOS.stream().filter(reportDatasetDO -> ds.getId().equals(reportDatasetDO.getDatasourceId())).toList();
            // 忽略转换的字段名，手动转列表
            String ignorePropertieName = "datasets";
            switch (ds.getType()) {
                case JDBC -> {
                    // 转换对应的ureport对象
                    JdbcDatasourceDefinition jdbcDatasourceDefinition = BeanUtil.copyProperties(ds, JdbcDatasourceDefinition.class, ignorePropertieName);
                    datasetDefinitions.addAll(BeanUtils.toBean(datasetInDatasourceList, SqlDatasetDefinition.class));
                    jdbcDatasourceDefinition.setDatasets(datasetDefinitions);
                    datasourceDefinitions.add(jdbcDatasourceDefinition);
                }
                case SPRING -> {
                    // 转换对应的ureport对象
                    SpringBeanDatasourceDefinition springBeanDatasourceDefinition = BeanUtil.copyProperties(ds, SpringBeanDatasourceDefinition.class, ignorePropertieName);
                    datasetDefinitions.addAll(BeanUtils.toBean(datasetInDatasourceList, BeanDatasetDefinition.class));
                    springBeanDatasourceDefinition.setDatasets(datasetDefinitions);
                    datasourceDefinitions.add(springBeanDatasourceDefinition);
                }
                case API -> {
                    // 转换对应的ureport对象
                    ApiDatasourceDefinition apiDatasourceDefinition = BeanUtil.copyProperties(ds, ApiDatasourceDefinition.class, ignorePropertieName);
                    datasetDefinitions.addAll(BeanUtils.toBean(datasetInDatasourceList, ApiDatasetDefinition.class));
                    apiDatasourceDefinition.setDatasets(datasetDefinitions);
                    datasourceDefinitions.add(apiDatasourceDefinition);
                }
            }
        }
        reportDefinition.setDatasources(datasourceDefinitions);
    }

    @Override
    public HtmlReport commonPreview(ReportTemplateCommonPreviewReqVO reqVO) {
        // 查询模板
        ReportTemplateDO reportTemplateDO = validateTemplateExists(reqVO.getTemplateId());
        if (ObjectUtil.isNull(reportTemplateDO)) {
            throw exception(TEMPLATE_NOT_EXISTS);
        }
        // 检查模板内容
        String content = reportTemplateDO.getContent();
        if (StringUtils.isEmpty(content)) {
            throw exception(TEMPLATE_PREVIEW_TEMPLATE_CONTENT_EMPTY_ERROR);
        }
        // 自定义请求vo转ureport对象
        PreviewParameters params = BeanUtils.toBean(reqVO, PreviewParameters.class);
        params.setContent(content);

        // 生成html
        HtmlProducer htmlProducer = new HtmlProducer();
        ReportRender reportRender = new ReportRender();
        ReportDefinition reportDefinition = getReportDefinition(reportRender, params, false);

        Map<String, Object> query = params.getQuery();
        // 检查表单配置
        checkSearchForm(reportDefinition.getSearchForm(), query);
        // 增加多租户参数
        tenantParamsUtils.prepareTenantParam(query);
        // 匹配数据源、数据集
        queryDatasource(reportTemplateDO, reportDefinition);

        Report report = reportRender.render(reportDefinition, query);
        return htmlProducer.produce(reportDefinition, report);
    }

    /**
     * 检查报表模板定义的表单参数是否为空
     *
     * @param searchForm 模板定义的表单参数
     * @param parameters 表单查询参数
     */
    private void checkSearchForm(SearchForm searchForm, Map<String, Object> parameters) {
        if (ObjectUtil.isNotNull(searchForm)) {
            List<Component> components = Optional.ofNullable(searchForm.getComponents()).orElse(new ArrayList<>());
            for (Component component : components) {
                checkComponent(component, parameters);
            }
        }
    }

    /**
     * 检查表单中的组件明细
     *
     * @param component
     * @param parameters
     */
    private void checkComponent(Component component, Map<String, Object> parameters) {
        InputComponent inputComponent = (InputComponent) component;
        if (ObjectUtil.isNotNull(inputComponent)) {
            String bindParameter = inputComponent.getBindParameter();
            if (StringUtils.isNotEmpty(bindParameter)) {
                if (inputComponent instanceof TextInputComponent) {
                    Object bindValue = parameters.get(bindParameter);
                    if (parameters.containsKey(bindParameter) && ObjectUtil.isNotNull(bindValue) && StringUtils.isEmpty(String.valueOf(bindValue))) {
                        throw ServiceExceptionUtil.exception(TEMPLATE_PREIVEW_QUERYPARAM_EMPTY, bindParameter);
                    }
                }
            }
        }
    }


    @Override
    public void print(ReportTemplatePreviewReqVO reqVO, HttpServletResponse response) {
        // 自定义请求vo转ureport对象
        PreviewParameters params = BeanUtils.toBean(reqVO, PreviewParameters.class);

        long start = System.currentTimeMillis();
        ReportRender reportRender = new ReportRender();
        ReportDefinition reportDefinition = getReportDefinition(reportRender, params, true);
        Map<String, Object> query = params.getQuery();
        // 检查表单配置
        checkSearchForm(reportDefinition.getSearchForm(), query);
        // 增加多租户参数
        tenantParamsUtils.prepareTenantParam(query);
        // 匹配数据源、数据集
        queryPreviewDatasource(query, reportDefinition);

        Report report = reportRender.render(reportDefinition, query);
        Map<String, String> chartImages = params.getChartImages();
        report.setChartImages(chartImages);
        try {
            OutputStream out = response.getOutputStream();
            response.setCharacterEncoding(UTF8_CHARSET);
            response.setContentType("application/pdf; charset=UTF-8");
            // 创建下载文件
            ProducerEnum p = ProducerEnum.PDF;
            String downFileName = com.bstek.common.utils.StringUtils.randomFileName() + com.bstek.common.utils.StringUtils.getFileSuffix(p);
            response.setHeader(Header.CONTENT_DISPOSITION.getValue(), ATTACHMENT_PREFIX + URLEncoder.encode(downFileName, UTF8_CHARSET));

            response.setHeader("code", DOWNLOAD_SUCCESS_HEADER_CODE);
            ExportUtils.export(out, report, p);
            long end = System.currentTimeMillis();
            ToolUtils.logToConsole("获取打印文件耗时:" + (end - start) + "ms");
        } catch (Exception ex) {
            log.error("模版预览打印异常", ex);
            throw new ReportException(ex);
        }
    }

    @Override
    public void commonPrint(ReportTemplateCommonPreviewReqVO reqVO, HttpServletResponse response) {
        // 查询模板
        ReportTemplateDO reportTemplateDO = validateTemplateExists(reqVO.getTemplateId());
        if (ObjectUtil.isNull(reportTemplateDO)) {
            throw exception(TEMPLATE_NOT_EXISTS);
        }
        // 检查模板内容
        String content = reportTemplateDO.getContent();
        if (StringUtils.isEmpty(content)) {
            throw exception(TEMPLATE_PREVIEW_TEMPLATE_CONTENT_EMPTY_ERROR);
        }
        // 自定义请求vo转ureport对象
        PreviewParameters params = BeanUtils.toBean(reqVO, PreviewParameters.class);
        params.setContent(content);

        long start = System.currentTimeMillis();
        ReportRender reportRender = new ReportRender();
        ReportDefinition reportDefinition = getReportDefinition(reportRender, params, false);
        Map<String, Object> query = params.getQuery();
        // 检查表单配置
        checkSearchForm(reportDefinition.getSearchForm(), query);
        // 增加多租户参数
        tenantParamsUtils.prepareTenantParam(query);
        // 匹配数据源、数据集
        queryDatasource(reportTemplateDO, reportDefinition);

        Report report = reportRender.render(reportDefinition, query);
        Map<String, String> chartImages = params.getChartImages();
        report.setChartImages(chartImages);
        try {
            OutputStream out = response.getOutputStream();
            response.setCharacterEncoding(UTF8_CHARSET);
            response.setContentType("application/pdf; charset=UTF-8");
            // 创建下载文件
            ProducerEnum p = ProducerEnum.PDF;
            String downFileName = com.bstek.common.utils.StringUtils.randomFileName() + com.bstek.common.utils.StringUtils.getFileSuffix(p);
            response.setHeader(Header.CONTENT_DISPOSITION.getValue(), ATTACHMENT_PREFIX + URLEncoder.encode(downFileName, UTF8_CHARSET));

            response.setHeader("code", DOWNLOAD_SUCCESS_HEADER_CODE);
            ExportUtils.export(out, report, p);
            long end = System.currentTimeMillis();
            ToolUtils.logToConsole("获取打印文件耗时:" + (end - start) + "ms");
        } catch (Exception ex) {
            log.error("模版预览打印异常", ex);
            throw new ReportException(ex);
        }
    }

    @Override
    public void download(String type, ReportTemplatePreviewReqVO reqVO, HttpServletResponse response) {
        // 自定义请求vo转ureport对象
        PreviewParameters params = BeanUtils.toBean(reqVO, PreviewParameters.class);

        ReportRender reportRender = new ReportRender();
        ReportDefinition reportDefinition = getReportDefinition(reportRender, params, true);
        Map<String, Object> query = params.getQuery();
        // 检查表单配置
        checkSearchForm(reportDefinition.getSearchForm(), query);
        // 增加多租户参数
        tenantParamsUtils.prepareTenantParam(query);
        // 匹配数据源、数据集
        queryPreviewDatasource(query, reportDefinition);

        Report report = reportRender.render(reportDefinition, query);
        Map<String, String> chartImages = params.getChartImages();
        report.setChartImages(chartImages);
        try {
            OutputStream out = response.getOutputStream();
            response.setCharacterEncoding(UTF8_CHARSET);
            response.setContentType("application/octet-stream; charset=UTF-8");
            // 创建下载文件
            ProducerEnum p = ProducerEnum.valueOf(type.toUpperCase());
            String downFileName = com.bstek.common.utils.StringUtils.randomFileName() + com.bstek.common.utils.StringUtils.getFileSuffix(p);
            response.setHeader(Header.CONTENT_DISPOSITION.getValue(), ATTACHMENT_PREFIX + URLEncoder.encode(downFileName, UTF8_CHARSET));
            response.setHeader("code", DOWNLOAD_SUCCESS_HEADER_CODE);
            long start = System.currentTimeMillis();
            ExportUtils.export(out, report, p);
            long end = System.currentTimeMillis();
            ToolUtils.logToConsole("下载 " + type + " 报表耗时:" + (end - start) + "ms");
        } catch (Exception ex) {
            log.error("模版预览下载文件异常", ex);
            throw new ReportException(ex);
        }
    }

    @Override
    public void commonDownload(String type, ReportTemplateCommonPreviewReqVO reqVO, HttpServletResponse response) {
        // 查询模板
        ReportTemplateDO reportTemplateDO = validateTemplateExists(reqVO.getTemplateId());
        if (ObjectUtil.isNull(reportTemplateDO)) {
            throw exception(TEMPLATE_NOT_EXISTS);
        }
        // 检查模板内容
        String content = reportTemplateDO.getContent();
        if (StringUtils.isEmpty(content)) {
            throw exception(TEMPLATE_PREVIEW_TEMPLATE_CONTENT_EMPTY_ERROR);
        }
        // 自定义请求vo转ureport对象
        PreviewParameters params = BeanUtils.toBean(reqVO, PreviewParameters.class);
        params.setContent(content);

        ReportRender reportRender = new ReportRender();
        ReportDefinition reportDefinition = getReportDefinition(reportRender, params, true);
        Map<String, Object> query = params.getQuery();
        // 检查表单配置
        checkSearchForm(reportDefinition.getSearchForm(), query);
        // 增加多租户参数
        tenantParamsUtils.prepareTenantParam(query);
        // 匹配数据源、数据集
        queryDatasource(reportTemplateDO, reportDefinition);

        Report report = reportRender.render(reportDefinition, query);
        Map<String, String> chartImages = params.getChartImages();
        report.setChartImages(chartImages);
        try {
            OutputStream out = response.getOutputStream();
            response.setCharacterEncoding(UTF8_CHARSET);
            response.setContentType("application/octet-stream; charset=UTF-8");
            // 创建下载文件
            ProducerEnum p = ProducerEnum.valueOf(type.toUpperCase());
            String downFileName = com.bstek.common.utils.StringUtils.randomFileName() + com.bstek.common.utils.StringUtils.getFileSuffix(p);
            response.setHeader(Header.CONTENT_DISPOSITION.getValue(), ATTACHMENT_PREFIX + URLEncoder.encode(downFileName, UTF8_CHARSET));
            response.setHeader("code", DOWNLOAD_SUCCESS_HEADER_CODE);
            long start = System.currentTimeMillis();
            ExportUtils.export(out, report, p);
            long end = System.currentTimeMillis();
            ToolUtils.logToConsole("下载 " + type + " 报表耗时:" + (end - start) + "ms");
        } catch (Exception ex) {
            log.error("模版预览下载文件异常", ex);
            throw new ReportException(ex);
        }
    }

    @Override
    public void exportTemplate(Long templateId, HttpServletResponse response) {
        ReportTemplateDO reportTemplateDO = validateTemplateExists(templateId);
        if (ObjectUtil.isNull(reportTemplateDO)) {
            throw exception(TEMPLATE_NOT_EXISTS);
        }
        PrintWriter writer = null;
        try {
            // 模版关键信息
            JSONObject exportObj = new JSONObject();
            exportObj.put("content", reportTemplateDO.getContent());
            Set<Long> datasourceIds = reportTemplateDO.getDatasourceIds();
            exportObj.put(DATASOURCEIDS_FIELD_NAME, CollUtil.isNotEmpty(datasourceIds) ? datasourceIds : Set.of());
            Set<Long> datasetIds = reportTemplateDO.getDatasetIds();
            exportObj.put(DATASETIDS_FIELD_NAME, CollUtil.isNotEmpty(datasetIds) ? datasetIds : Set.of());

            // 设置响应的内容类型为 application/json
            response.setContentType(ContentType.JSON.getValue());
            response.setCharacterEncoding(UTF8_CHARSET);
            response.setHeader(Header.CONTENT_DISPOSITION.getValue(), ATTACHMENT_PREFIX + URLEncoder.encode(String.format("%s.json", reportTemplateDO.getName()), UTF8_CHARSET));

            // 写入响应的输出流
            writer = response.getWriter();
            writer.print(exportObj.toJSONString());
            writer.flush();
        } catch (Exception ex) {
            log.error("模板导出异常", ex);
            throw new ReportException(ex);
        } finally {
            IoUtil.close(writer);
        }
    }

    @Override
    public void importTemplate(String name, MultipartFile file) {
        try {
            // 读取文件json
            String templateStr = IoUtil.read(file.getInputStream(), UTF8_CHARSET);
            JSONObject jsonObject = JSON.parseObject(templateStr);

            boolean isSuperAdmin = SecurityFrameworkUtils.isSuperAdmin();

            ReportTemplateSaveReqVO createReqVO = new ReportTemplateSaveReqVO();
            createReqVO.setName(name);
            // 校验模板名唯一性
            getSelf().validateTemplateNameUnique(createReqVO, isSuperAdmin);
            // 复制信息
            String content = jsonObject.getString("content");
            if (StringUtils.isEmpty(content)) {
                throw new ServiceException(TEMPLATE_IMPORT_CONTENT_EMPTY_ERROR);
            }
            // 生成id
            createReqVO.setId((Long) snowFlakeGenerator.nextId(null));
            createReqVO.setContent(content);
            createReqVO.setDatasetIds(jsonObject.getJSONArray(DATASETIDS_FIELD_NAME).stream().map(s -> Long.parseLong(s.toString())).collect(Collectors.toSet()));
            createReqVO.setDatasourceIds(jsonObject.getJSONArray(DATASOURCEIDS_FIELD_NAME).stream().map(s -> Long.parseLong(s.toString())).collect(Collectors.toSet()));
            // 新增模板
            ReportTemplateDO template = BeanUtils.toBean(createReqVO, ReportTemplateDO.class);
            // 超管创建的作为内置模板
            if (isSuperAdmin) {
                template.setType(ReportTemplateTypeEnum.SYSTEM);
            } else {
                template.setType(ReportTemplateTypeEnum.CUSTOM);
            }
            templateMapper.insert(template);
        } catch (JSONException je) {
            throw new ReportException("文件解析失败，请检查文件格式和内容");
        } catch (ServiceException sex) {
            throw sex;
        } catch (Exception ex) {
            log.error("模板导入异常", ex);
            throw new ReportException(ex);
        }
    }

    /**
     * 获取报表定义
     *
     * @param reportRender 报表渲染器
     * @param params       预览参数
     * @param needDecode   是否需要解码（从前端直接拿来的需要url解码）
     * @return
     */
    private ReportDefinition getReportDefinition(ReportRender reportRender, PreviewParameters params, boolean needDecode) {
        ReportDefinition reportDefinition;

        String content = params.getContent();
        if (StringUtils.isNotBlank(content)) {
            if (needDecode) {
                content = URLUtil.decode(content);
            }
            reportDefinition = reportRender.getReportDefinition(content, "utf-8");
        } else {
            throw new ReportComputeException("模版文件不能为空");
        }
        if (reportDefinition == null) {
            throw new ReportDesignException("模板数据已过期，无法预览");
        }
        Map<String, Object> parameters = params.getQuery();
        if (parameters == null) {
            parameters = new HashMap<>();
            params.setQuery(parameters);
        }
        Map<String, String> component = params.getComponent();
        if (component == null) {
            component = new HashMap<>();
            params.setComponent(component);
        }
        // 相同的低优先级的参数会被高优先级参数覆盖
        // 参数优先级， token参数 > 页面表单参数 > url链接参数 > 数据集默认参数
        parameters.put("pageIndex", params.getPageIndex());
        return reportDefinition;
    }
}
