package jnpf.cultivate.v2.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jnpf.cultivate.service.TeachingRecordService;
import jnpf.cultivate.service.TeachingSkillService;
import jnpf.cultivate.v2.service.FtbCultivateLabelService;
import jnpf.cultivate.v2.service.V2TeachingSkillService;
import jnpf.entity.cultivate.TeachingRecord;
import jnpf.entity.cultivate.TeachingSkill;
import jnpf.exception.HandleException;
import jnpf.model.cultivate.po.label.FtbCultivateLabel;
import jnpf.model.cultivate.v2.exam.req.SkillDto;
import jnpf.model.cultivate.v2.exam.vo.ErrorObjectVo;
import jnpf.model.cultivate.v2.exam.vo.ImportObjectVo;
import jnpf.model.cultivate.v2.exam.vo.SkillUploadInfoVo;
import jnpf.model.cultivate.v2.exam.vo.SkillVo;
import jnpf.model.cultivate.v2.label.vo.FtbCultivateLabelVo;
import jnpf.util.*;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 技能点服务实现[v2]
 *
 * @author yanwenfu
 * @create 2026-02-28
 */
@Service
public class V2TeachingSkillServiceImpl implements V2TeachingSkillService {

    @Resource
    private TeachingSkillService teachingSkillService;
    @Resource
    private FtbCultivateLabelService cultivateLabelService;
    @Resource
    private TeachingRecordService teachingRecordService;

    private static final Pattern EMOJI_PATTERN = Pattern.compile(
            "[\uD83C\uDF00-\uD83D\uDDFF]|" +
                    "[\uD83D\uDE00-\uD83D\uDE4F]|" +
                    "[\uD83D\uDE80-\uD83D\uDEFF]|" +
                    "[\u2600-\u26FF]|" +
                    "[\u2700-\u27BF]",
            Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE
    );

    @Override
    public SkillUploadInfoVo teachingSkillImport(MultipartFile file) throws Exception {

        SkillUploadInfoVo uploadInfo = new SkillUploadInfoVo();

        try (InputStream is = file.getInputStream();
             XSSFWorkbook workbook = new XSSFWorkbook(is)) {
            // 获取数据
            XSSFSheet sheet = workbook.getSheetAt(0);
            List<String[]> dataFromSheet = TemplateExcelUtils.getDataFromSheetWithNullRow(sheet);
            if (dataFromSheet.isEmpty() || dataFromSheet.size() == 1) {
                throw new HandleException("未获取到工作簿数据");
            }
            List<ImportObjectVo> list = new ArrayList<>();
            Set<String> set = new HashSet<>();
            String[] row1 = dataFromSheet.get(0);
            if (row1.length < 3 || !row1[0].equals("*技能点名称") || !row1[1].equals("分类") || !row1[2].equals("技能点描述")) {
                throw new HandleException("当前模板非技能点导入模板，请上传正确模板及内容");
            }
            for (int i = 1; i < dataFromSheet.size(); i++) {
                List<String> array = List.of(dataFromSheet.get(i));
                if (StringUtil.isEmpty(array.get(0)) && StringUtil.isEmpty(array.get(1)) && StringUtil.isEmpty(array.get(2))) {
                    continue;
                }
                ImportObjectVo importObj = new ImportObjectVo();
                importObj.setIndex(i);
                String errorMsg = "";
                for (int j = 0; j < array.size(); j++) {
                    String str = array.get(j);
                    if (j == 0) {
                        // 技能点名称
                        String msg = checkStr(i, j, str, 20, errorMsg);
                        if (StringUtil.isNotEmpty(msg)) {
                            errorMsg += msg;
                        }
                        importObj.setSkillName(str);
                    }
                    if (j == 1) {
                        // 技能点分类
                        if (StringUtil.isNotEmpty(str)) {
                            importObj.setCategoryName(str);
                        }
                    }
                    if (j == 2) {
                        // 技能点描述
                        String msg = checkStr(i, j, str, 500, errorMsg);
                        if (StringUtil.isNotEmpty(msg)) {
                            errorMsg += StringUtil.isNotEmpty(errorMsg) ? "，" + msg : msg;
                        }
                        importObj.setDescription(str);
                    }
                }
                // 报错了
                if (StringUtil.isNotEmpty(errorMsg)) {
                    ErrorObjectVo errorObj = JsonUtil.getJsonToBean(importObj, ErrorObjectVo.class);
                    errorObj.setErrorReason(errorMsg);
                    uploadInfo.getFailList().add(errorObj);
                    uploadInfo.setFailNum(uploadInfo.getFailNum() + 1);
                    continue;
                }
                // 说明内容未重复
                if (set.add(importObj.getSkillName())) {
                    list.add(importObj);
                } else {
                    ErrorObjectVo errorObj = JsonUtil.getJsonToBean(importObj, ErrorObjectVo.class);
                    errorObj.setErrorReason("第" + (i + 1) + "行 技能点名称重复");
                    uploadInfo.getFailList().add(errorObj);
                    uploadInfo.setFailNum(uploadInfo.getFailNum() + 1);
                }
            }
            if (!list.isEmpty()) {
                // 判断list size + db size是否大于100 舍弃大于100的
                Long dbCount = getDbCount();
                // 还能导入的条数
                int insertCount = 100 - dbCount.intValue();
                if (insertCount <= 0) {
                    // 数据库配置数已满, 无法导入
                    // -1 是因为第一条是表头
                    uploadInfo.setFailNum(uploadInfo.getFailNum() + list.size());
                    list.forEach(v -> {
                        ErrorObjectVo errorObj = JsonUtil.getJsonToBean(v, ErrorObjectVo.class);
                        errorObj.setErrorReason("已超过技能点配置最大条数（100）限制");
                        uploadInfo.getFailList().add(errorObj);
                    });
                    uploadInfo.getFailList().sort(Comparator.comparing(ErrorObjectVo::getIndex));
                    // 生成失败excel
                    String failBase64 = generateFailExcelBase64(uploadInfo.getFailList());
                    uploadInfo.setErrorExcelBase64(failBase64);
                    return uploadInfo;
                }
                // 判断list中有没有与数据库重复的
                List<Integer> exceptIndex = teachingSkillService.getDistinctCountBatch(list);
                // 去除重复的
                if (!exceptIndex.isEmpty()) {
                    addFailDataToList(list, uploadInfo, exceptIndex, "技能点名称重复");
                }
                // 验证category
                List<String> categoryNameList = list.stream().map(ImportObjectVo::getCategoryName).filter(StringUtil::isNotEmpty).distinct().collect(Collectors.toList());
                Map<String, FtbCultivateLabel> labelMap = cultivateLabelService.getLabelMapByName(categoryNameList);
                List<Integer> categoryErrorIndex = new ArrayList<>();
                list.forEach(v -> {
                    if (StringUtil.isNotEmpty(v.getCategoryName())) {
                        FtbCultivateLabel label = labelMap.get(v.getCategoryName());
                        if (null == label) {
                            categoryErrorIndex.add(v.getIndex());
                        }
                    }
                });
                if (!categoryErrorIndex.isEmpty()) {
                    addFailDataToList(list, uploadInfo, categoryErrorIndex, "分类异常");
                }
                // 不可导入的条数
                int overflowCount = insertCount - list.size();
                if (overflowCount < 0) {
                    overflowCount = Math.abs(overflowCount);
                    // 被截掉的部分，取最后 exceptCount 条
                    List<Integer> removedIndexes = list.subList(list.size() - overflowCount, list.size())
                            .stream()
                            .map(ImportObjectVo::getIndex)
                            .collect(Collectors.toList());
                    addFailDataToList(list, uploadInfo, removedIndexes, "已超过文字配置最大条数（100）限制");
                }
            }
            if (uploadInfo.getFailNum() > 0) {
                uploadInfo.getFailList().sort(Comparator.comparing(ErrorObjectVo::getIndex));
                // 生成失败excel
                String failBase64 = generateFailExcelBase64(uploadInfo.getFailList());
                uploadInfo.setErrorExcelBase64(failBase64);
            }
            if (!list.isEmpty()) {
                uploadInfo.setSuccessNum(list.size());
                // 导入成功的数据
                teachingSkillService.saveImportData(list);
            }
        } catch (Exception e) {
            if (e instanceof HandleException) {
                throw new HandleException(e.getMessage());
            } else {
                throw new HandleException("导入转换工作簿异常");
            }
        }
        return uploadInfo;
    }

    @Override
    public String templateDownload() throws Exception {

        // 生成工作簿
        TemplateWorkSheet workSheet = new TemplateWorkSheet();
        workSheet.setSheetName(ConstantUtil.NAME_SKILL_IMPORT);
        // 生成表头
        workSheet.setRowNames(ConstantUtil.TITLE_SKILL_IMPORT);
        workSheet.setTitleNum(1);
        // 生成数据
        List<Object[]> dataList = new ArrayList<>();
        // 查询分类列表
        LambdaQueryWrapper<FtbCultivateLabel> labelList = new LambdaQueryWrapper<FtbCultivateLabel>()
                .eq(FtbCultivateLabel::getEnabledMark, ConstantUtil.NUM_FALSE)
                .eq(FtbCultivateLabel::getType, 3)
                .orderByDesc(FtbCultivateLabel::getCreatorTime);
        List<FtbCultivateLabel> list = cultivateLabelService.list(labelList);
        if (!list.isEmpty()) {
            String[] names = list.stream().map(FtbCultivateLabel::getName).toArray(String[]::new);
            int[] coordinate = new int[]{1, 100, 1, 1};
            workSheet.setDropDownListMap(Map.of(names, coordinate));
        }
        workSheet.setDataList(dataList);
        return excelBase64(List.of(workSheet));
    }

    @Override
    public List<SkillVo> getDownSkillList(SkillDto skillDto) {

        String skillId = null;
        if (!StringUtil.isBlank(skillDto.getBizId())) {
            // 查询bizId关联的技能点
            TeachingRecord record = teachingRecordService.getOne(new LambdaQueryWrapper<TeachingRecord>().eq(TeachingRecord::getId, skillDto.getBizId()));
            if (null != record) {
                TeachingSkill skill = teachingSkillService.getOne(new LambdaQueryWrapper<TeachingSkill>().eq(TeachingSkill::getId, record.getSkillId()));
                if (StringUtil.isBlank(skillDto.getCategoryId())) {
                    skillId = record.getSkillId();
                } else {
                    if (("-1".equals(skillDto.getCategoryId()) && StringUtil.isBlank(skill.getCategoryId()))
                            || (StringUtil.isNotBlank(skill.getCategoryId()) && skill.getCategoryId().equals(skillDto.getCategoryId()))) {
                        skillId = record.getSkillId();
                    }
                }
            }
        }
        return teachingSkillService.getDownSkillList(skillId, skillDto.getCategoryId());
    }

    @Override
    public List<FtbCultivateLabelVo> getDownCategoryList(Integer searchSource) {

        List<FtbCultivateLabelVo> list = cultivateLabelService.getList(ConstantUtil.LABEL_SKILL, searchSource);
        if (null == list || list.isEmpty()) {
            return List.of(new FtbCultivateLabelVo(ConstantUtil.DEFAULT_ID));
        }
        List<String> categoryIds = list.stream().map(FtbCultivateLabelVo::getId).collect(Collectors.toList());
        List<String> collect = teachingSkillService.getCategoryHasChild(categoryIds);
        list.removeIf(v -> !collect.contains(v.getId()));
        list.add(new FtbCultivateLabelVo(ConstantUtil.DEFAULT_ID));
        return list;
    }

    private String excelBase64(List<TemplateWorkSheet> workSheetList) throws Exception {

        Workbook workbook = TemplateExcelUtils.createWorkBook(workSheetList, false);
        if (workbook == null) {
            return null;
        }
        try (workbook; ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            workbook.write(bos);
            return Base64.getEncoder().encodeToString(bos.toByteArray());
        }
    }

    private String generateFailExcelBase64(List<ErrorObjectVo> failList) throws Exception {

        if (null == failList || failList.isEmpty()) {
            return null;
        }
        // 生成工作簿
        TemplateWorkSheet workSheet = new TemplateWorkSheet();
        workSheet.setSheetName(ConstantUtil.NAME_SKILL_ERROR);
        // 生成表头
        workSheet.setRowNames(ConstantUtil.TITLE_SKILL_ERROR);
        workSheet.setTitleNum(1);
        // 生成数据
        List<Object[]> dataList = new ArrayList<>();
        // 数据源
        failList.forEach(fail -> {
            Object[] objArray = new Object[]{fail.getSkillName(), fail.getCategoryName(), fail.getDescription(), fail.getErrorReason()};
            dataList.add(objArray);
        });
        workSheet.setDataList(dataList);
        return excelBase64(List.of(workSheet));
    }

    private void addFailDataToList(List<ImportObjectVo> list, SkillUploadInfoVo uploadInfo, List<Integer> exceptIndex, String reason) {
        List<ImportObjectVo> removeList = list.stream().filter(v -> exceptIndex.contains(v.getIndex()))
                .sorted(Comparator.comparing(ImportObjectVo::getIndex))
                .collect(Collectors.toList());
        removeList.forEach(v -> {
            ErrorObjectVo errorObj = JsonUtil.getJsonToBean(v, ErrorObjectVo.class);
            errorObj.setErrorReason("第" + (v.getIndex() + 1) + "行 " + reason);
            uploadInfo.getFailList().add(errorObj);
        });
        list.removeAll(removeList);
        uploadInfo.setFailNum(uploadInfo.getFailNum() + exceptIndex.size());
    }

    private String getKey() {

        return UserProvider.getLoginUserId() + "-" + FtbUtil.getId();
    }

    private Long getDbCount() {
        LambdaQueryWrapper<TeachingSkill> queryWrapper = new LambdaQueryWrapper<TeachingSkill>()
                .eq(TeachingSkill::getDeleteMark, ConstantUtil.NUM_FALSE);
        return teachingSkillService.count(queryWrapper);
    }

    /**
     * 检查内容是否符合规范
     * @param row 行
     * @param col 列(0: 标题, 1: 内容)
     * @param str 内容
     * @param wordLimit 允许字数
     * @return java.lang.String
     */
    private String checkStr(int row, int col, String str, int wordLimit, String errorMsg) {

        String msg = "";
        int realLength;
        // 判断内容是否为空
        if (StringUtil.isEmpty(str)) {
            msg += col == 0 ? "技能点名称为空" : "";
            realLength = 0;
        } else {
            realLength = str.codePointCount(0, str.length());
        }
        // 判断字数是否超过限制
        if (realLength > wordLimit) {
            if (col == 0) {
                msg += "技能点名称超过字数(" + wordLimit + ")上限";
            }
            if (col == 2) {
                msg += "技能点描述超过字数(" + wordLimit + ")上限";
            }
        }
        // 判断是否包含emoji表情
        if (containsEmoji(str)) {
            msg += col == 0 ? "技能点名称中包含emoji表情" : "";
        }
        if (StringUtil.isEmpty(errorMsg) && StringUtil.isNotEmpty(msg)) {
            msg = "第" + (row + 1) + "行 " + msg;
        }
        return msg;
    }

    public boolean containsEmoji(String text) {
        if (text == null) {
            return false;
        }
        return EMOJI_PATTERN.matcher(text).find();
    }
}
