package com.hotwj.platform.reportStatistics.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
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.hotwj.platform.reportStatistics.domain.SysReportTemplate;
import com.hotwj.platform.reportStatistics.domain.bo.SysReportTemplateBo;
import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateSyncCompareVo;
import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateVo;
import com.hotwj.platform.reportStatistics.mapper.SysReportTemplateMapper;
import com.hotwj.platform.reportStatistics.service.IReportDataProvider;
import com.hotwj.platform.reportStatistics.service.ISysReportTemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 报表模板Service业务层处理
 *
 * @author shihongwei
 * @date 2024-03-15
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class SysReportTemplateServiceImpl implements ISysReportTemplateService {

    private static final String CLASSPATH_SCAN_REMARK = "Synced from classpath scan";
    private static final String PROVIDER_REMARK_PREFIX = "Synced from Provider: ";

    private final SysReportTemplateMapper baseMapper;
    private final List<IReportDataProvider> reportDataProviders;

    /**
     * 根据模板编码查找对应的 Provider
     */
    @Override
    public IReportDataProvider findProviderByTemplateCode(String templateCode) {
        if (reportDataProviders == null || StringUtils.isBlank(templateCode)) {
            return null;
        }
        for (IReportDataProvider provider : reportDataProviders) {
            if (templateCode.equals(provider.getTemplateCode())) {
                return provider;
            }
        }
        return null;
    }

    /**
     * 查询报表模板
     */
    @Override
    public SysReportTemplateVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    /**
     * 查询报表模板
     */
    @Override
    public SysReportTemplateVo queryByCode(String code) {
        return baseMapper.selectVoOne(new LambdaQueryWrapper<SysReportTemplate>().eq(SysReportTemplate::getTemplateCode, code));
    }

    /**
     * 查询报表模板列表
     */
    @Override
    public TableDataInfo<SysReportTemplateVo> queryPageList(SysReportTemplateBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<SysReportTemplate> lqw = buildQueryWrapper(bo);
        Page<SysReportTemplateVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询报表模板列表
     */
    @Override
    public List<SysReportTemplateVo> queryList(SysReportTemplateBo bo) {
        LambdaQueryWrapper<SysReportTemplate> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<SysReportTemplate> buildQueryWrapper(SysReportTemplateBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<SysReportTemplate> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getTemplateCode()), SysReportTemplate::getTemplateCode, bo.getTemplateCode());
        lqw.like(StringUtils.isNotBlank(bo.getTemplateName()), SysReportTemplate::getTemplateName, bo.getTemplateName());
        lqw.eq(StringUtils.isNotBlank(bo.getRemark()), SysReportTemplate::getRemark, bo.getRemark());
        return lqw;
    }

    /**
     * 新增报表模板
     */
    @Override
    public Boolean insertByBo(SysReportTemplateBo bo) {
        SysReportTemplate add = MapstructUtils.convert(bo, SysReportTemplate.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改报表模板
     */
    @Override
    public Boolean updateByBo(SysReportTemplateBo bo) {
        SysReportTemplate update = MapstructUtils.convert(bo, SysReportTemplate.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(SysReportTemplate entity) {
        //TODO 做一些数据校验,如唯一约束
    }

    /**
     * 批量删除报表模板
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteByIds(ids) > 0;
    }

    /**
     * 同步 Classpath 下的模版到数据库
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void syncTemplatesFromClasspath() {
        syncTemplatesFromClasspath(null);
    }

    @Override
    public List<SysReportTemplateSyncCompareVo> querySyncDiffList() {
        LinkedHashMap<String, LocalTemplateSnapshot> localTemplates = loadLocalTemplateSnapshots();
        Map<String, SysReportTemplateVo> dbTemplates = queryExistingTemplates(null);

        Set<String> allCodes = new LinkedHashSet<>();
        allCodes.addAll(localTemplates.keySet());
        allCodes.addAll(dbTemplates.keySet());

        return allCodes.stream()
            .map(code -> buildCompareVo(code, localTemplates.get(code), dbTemplates.get(code)))
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void syncTemplatesFromClasspath(Collection<String> templateCodes) {
        LinkedHashMap<String, LocalTemplateSnapshot> localTemplates = loadLocalTemplateSnapshots();
        Set<String> targetCodes = normalizeTemplateCodes(templateCodes);
        if (targetCodes != null && targetCodes.isEmpty()) {
            throw new ServiceException("请选择需要同步的模板");
        }

        if (targetCodes != null) {
            List<String> missingCodes = targetCodes.stream()
                .filter(code -> !localTemplates.containsKey(code))
                .collect(Collectors.toList());
            if (!missingCodes.isEmpty()) {
                throw new ServiceException("以下模板在本地不存在，无法同步: " + String.join(", ", missingCodes));
            }
        }

        Map<String, SysReportTemplateVo> dbTemplates = queryExistingTemplates(targetCodes);

        for (LocalTemplateSnapshot snapshot : localTemplates.values()) {
            if (targetCodes != null && !targetCodes.contains(snapshot.getTemplateCode())) {
                continue;
            }

            SysReportTemplateVo existing = dbTemplates.get(snapshot.getTemplateCode());
            if (existing == null) {
                SysReportTemplate insert = new SysReportTemplate();
                insert.setTemplateCode(snapshot.getTemplateCode());
                insert.setTemplateName(snapshot.getTemplateName());
                insert.setContent(snapshot.getContent());
                insert.setMockData(snapshot.getMockData());
                insert.setVersion(1);
                insert.setRemark(snapshot.getRemark());
                baseMapper.insert(insert);
                log.info("Inserted template from local source: {}", snapshot.getTemplateCode());
                continue;
            }

            if (!hasDiff(snapshot, existing)) {
                continue;
            }

            SysReportTemplate update = new SysReportTemplate();
            update.setId(existing.getId());
            update.setTemplateCode(snapshot.getTemplateCode());
            update.setTemplateName(snapshot.getTemplateName());
            update.setContent(snapshot.getContent());
            update.setMockData(snapshot.getMockData());
            update.setRemark(snapshot.getRemark());
            update.setVersion(existing.getVersion() == null ? 1 : existing.getVersion() + 1);
            baseMapper.updateById(update);
            log.info("Updated template from local source: {}", snapshot.getTemplateCode());
        }
    }

    private SysReportTemplateSyncCompareVo buildCompareVo(String code, LocalTemplateSnapshot local, SysReportTemplateVo db) {
        List<String> diffTypes = buildDiffTypes(local, db);
        if (diffTypes.isEmpty()) {
            return null;
        }

        SysReportTemplateSyncCompareVo vo = new SysReportTemplateSyncCompareVo();
        vo.setTemplateCode(code);
        vo.setDbId(db == null ? null : db.getId());
        vo.setLocalTemplateName(local == null ? "" : local.getTemplateName());
        vo.setDbTemplateName(db == null ? "" : db.getTemplateName());
        vo.setLocalContent(local == null ? "" : local.getContent());
        vo.setDbContent(db == null ? "" : normalizeText(db.getContent()));
        vo.setLocalMockData(toJsonText(local == null ? null : local.getMockData()));
        vo.setDbMockData(toJsonText(db == null ? null : db.getMockData()));
        vo.setLocalRemark(local == null ? "" : local.getRemark());
        vo.setDbRemark(db == null ? "" : normalizeText(db.getRemark()));
        vo.setExistsInLocal(local != null);
        vo.setExistsInDb(db != null);
        vo.setSyncable(local != null);
        vo.setDiffTypes(diffTypes);
        return vo;
    }

    private boolean hasDiff(LocalTemplateSnapshot local, SysReportTemplateVo db) {
        return !buildDiffTypes(local, db).isEmpty();
    }

    private List<String> buildDiffTypes(LocalTemplateSnapshot local, SysReportTemplateVo db) {
        List<String> diffTypes = new ArrayList<>();
        if (local == null && db != null) {
            diffTypes.add("仅数据库");
            return diffTypes;
        }
        if (local != null && db == null) {
            diffTypes.add("仅本地");
            return diffTypes;
        }
        if (local == null) {
            return diffTypes;
        }

        if (!Objects.equals(normalizeText(local.getTemplateName()), normalizeText(db.getTemplateName()))) {
            diffTypes.add("名称");
        }
        if (!Objects.equals(normalizeText(local.getContent()), normalizeText(db.getContent()))) {
            diffTypes.add("内容");
        }
        if (!Objects.equals(toJsonText(local.getMockData()), toJsonText(db.getMockData()))) {
            diffTypes.add("模拟数据");
        }
        if (!Objects.equals(normalizeText(local.getRemark()), normalizeText(db.getRemark()))) {
            diffTypes.add("备注");
        }
        return diffTypes;
    }

    private Map<String, SysReportTemplateVo> queryExistingTemplates(Set<String> templateCodes) {
        LambdaQueryWrapper<SysReportTemplate> queryWrapper = Wrappers.lambdaQuery();
        if (templateCodes != null && !templateCodes.isEmpty()) {
            queryWrapper.in(SysReportTemplate::getTemplateCode, templateCodes);
        }
        return baseMapper.selectVoList(queryWrapper).stream()
            .collect(Collectors.toMap(SysReportTemplateVo::getTemplateCode, item -> item, (left, right) -> left, LinkedHashMap::new));
    }

    private LinkedHashMap<String, LocalTemplateSnapshot> loadLocalTemplateSnapshots() {
        LinkedHashMap<String, LocalTemplateSnapshot> templateMap = new LinkedHashMap<>();

        if (reportDataProviders != null) {
            for (IReportDataProvider provider : reportDataProviders) {
                try {
                    String code = provider.getTemplateCode();
                    if (StringUtils.isBlank(code)) {
                        continue;
                    }

                    String content = readTemplateContent(code);
                    if (StringUtils.isBlank(content)) {
                        log.warn("Skipping provider {} because template file {} not found", provider.getClass().getSimpleName(), code);
                        continue;
                    }

                    LocalTemplateSnapshot snapshot = new LocalTemplateSnapshot();
                    snapshot.setTemplateCode(code);
                    snapshot.setTemplateName(provider.getReportTitle());
                    snapshot.setContent(content);
                    snapshot.setMockData(provider.getMockData());
                    snapshot.setRemark(PROVIDER_REMARK_PREFIX + provider.getClass().getSimpleName());
                    templateMap.put(code, snapshot);
                } catch (Exception e) {
                    log.error("Error loading provider template: {}", provider.getClass().getSimpleName(), e);
                }
            }
        }

        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            Resource[] resources = resolver.getResources("classpath*:templates/**/*.vm");
            for (Resource resource : resources) {
                try {
                    String code = extractTemplateCode(resource);
                    if (StringUtils.isBlank(code) || templateMap.containsKey(code)) {
                        continue;
                    }

                    LocalTemplateSnapshot snapshot = new LocalTemplateSnapshot();
                    snapshot.setTemplateCode(code);
                    snapshot.setTemplateName(FileUtil.mainName(resource.getFilename()));
                    snapshot.setContent(IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8));
                    snapshot.setRemark(CLASSPATH_SCAN_REMARK);
                    templateMap.put(code, snapshot);
                } catch (Exception e) {
                    log.error("Error processing resource: {}", resource, e);
                }
            }
        } catch (IOException e) {
            log.error("Failed to load local templates", e);
            throw new ServiceException("读取本地模板失败");
        }

        return templateMap;
    }

    private String extractTemplateCode(Resource resource) throws IOException {
        String path = resource.getURL().getPath().replace("\\", "/");
        int index = path.lastIndexOf("templates/");
        if (index < 0) {
            return null;
        }
        return path.substring(index);
    }

    private String readTemplateContent(String code) {
        try {
            ClassPathResource resource = new ClassPathResource(code);
            if (!resource.exists()) {
                return null;
            }
            return IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.warn("Template file not found: {}", code, e);
            return null;
        }
    }

    private Set<String> normalizeTemplateCodes(Collection<String> templateCodes) {
        if (templateCodes == null) {
            return null;
        }
        return templateCodes.stream()
            .filter(StringUtils::isNotBlank)
            .collect(Collectors.toCollection(LinkedHashSet::new));
    }

    private String normalizeText(String value) {
        return value == null ? "" : value;
    }

    private String toJsonText(Object value) {
        if (value == null) {
            return "";
        }
        return JsonUtils.toJsonString(value);
    }

    @lombok.Data
    private static class LocalTemplateSnapshot {

        private String templateCode;

        private String templateName;

        private String content;

        private Object mockData;

        private String remark;
    }
}
