package com.yinlihupo.enlish.service.service.judge;

import com.yinlihupo.enlish.service.constant.ExamWordsConstant;
import com.yinlihupo.enlish.service.domain.dataobject.*;
import com.yinlihupo.enlish.service.domain.mapper.*;
import com.yinlihupo.enlish.service.model.bo.CoordinatesXY;
import com.yinlihupo.enlish.service.model.bo.StudentExamId;
import com.yinlihupo.enlish.service.model.bo.exam.ActionType;
import com.yinlihupo.enlish.service.model.bo.exam.DiagnosisResult;
import com.yinlihupo.enlish.service.model.bo.exam.ZoneStats;
import com.yinlihupo.enlish.service.service.ExamWordsJudgeService;
import com.yinlihupo.enlish.service.service.StudentLessonPlansService;
import com.yinlihupo.enlish.service.utils.PngUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {


    @Resource
    private ExamWordsJudgeResultDOMapper examWordsJudgeResultDOMapper;
    @Resource
    private StudentExamWordsDOMapper studentExamWordsDOMapper;
    @Resource
    private ExamWordsDOMapper examWordsDOMapper;
    @Resource
    private WordMasteryLogDOMapper wordMasteryLogDOMapper;
    @Resource
    private VocabularyBankDOMapper vocabularyBankDOMapper;
    @Resource
    private GradeUnitDOMapper gradeUnitDOMapper;
    @Resource
    private StudentDOMapper studentDOMapper;
    @Resource
    private PlanExamDOMapper planExamDOMapper;
    @Resource
    private StudentLessonPlansService studentLessonPlansService;

    @Value("${templates.data}")
    private String tessdataPath;

    @Override
    public void judgeExamWords(int count) {
        List<ExamWordsJudgeResultDO> examWordsJudgeResultDOS = examWordsJudgeResultDOMapper.selectUnfinishedExamWordsJudgeResultDOList(count);
        for (ExamWordsJudgeResultDO examWordsJudgeResultDO : examWordsJudgeResultDOS) {
            try {
                String ansSheetPath = examWordsJudgeResultDO.getAnsSheetPath();
                List<CoordinatesXY> coordinatesXIES = PngUtil.analysisXY(ansSheetPath);
                // 从图片中获取学生 id 和考试 id
                StudentExamId studentExamId = PngUtil.analyzeExamWordsIdAndStudentId(ansSheetPath, tessdataPath, coordinatesXIES);
                Integer examWordsJudgeResultDOId = examWordsJudgeResultDO.getId();
                if (studentExamId == null) {
                    log.info("未找到学生 id 和考试 id");
                    examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未识别学生和考试");
                    continue;
                }

                Integer studentId = studentExamId.getStudentId();
                Integer examWordsId = studentExamId.getExamId();
                StudentExamWordsDO studentExamWordsDO = studentExamWordsDOMapper.selectByStudentIdAndExamWordsId(studentId, examWordsId);
                if (studentExamWordsDO == null) {
                    examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未找到学生 id 和考试 id 对应的考试记录");
                    continue;
                }
                log.info("studentId:{},examWordsId:{}", studentId, examWordsId);
                if (studentExamWordsDO.getIsCompleted() == 1) {
                    examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "考试记录此前已识别");
                    continue;
                }

                ExamWordsDO examWordsDO = examWordsDOMapper.selectById(examWordsId);
                if(examWordsDO == null) {
                    examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未找到考试");
                    continue;
                }

                List<Integer> wordIds = examWordsDO.getWordIds();
                List<Integer> unmemorizedWordIds = PngUtil.analyzePngForUnmemorizedWordIds(ansSheetPath, wordIds, coordinatesXIES);
                List<Integer> memorizedWordIds = wordIds.stream().filter(wordId -> !unmemorizedWordIds.contains(wordId)).toList();

                ExamWordsJudgeResultDO wordsJudgeResultDO = ExamWordsJudgeResultDO.builder()
                        .id(examWordsJudgeResultDOId)
                        .studentId(studentId)
                        .examWordsId(examWordsId)
                        .correctWordIds(memorizedWordIds)
                        .wrongWordIds(unmemorizedWordIds)
                        .correctWordCount(memorizedWordIds.size())
                        .wrongWordCount(unmemorizedWordIds.size())
                        .isFinished(1)
                        .build();

                if (examWordsDO.getType().equals(ExamWordsConstant.EXAM_TYPE_BASELINE)) {
                    // 判断考试等级
                    judgeExamActualGrade(wordsJudgeResultDO, examWordsDO);
                } else {
                    wordsJudgeResultDO.setMsg("此次考试" + examWordsDO.getTitle() + "答对单词数为" + memorizedWordIds.size());
                }
                int updated = examWordsJudgeResultDOMapper.updateExamWordsJudgeResultDO(wordsJudgeResultDO);
                if (updated != 1) {
                    examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "更新考试记录失败");
                    continue;
                }
                log.info("更新考试记录成功");

                PlanExamDO planExamDO = planExamDOMapper.selectByExamId(examWordsId);
                if (planExamDO != null) {
                    studentLessonPlansService.finishStudentLessonPlan(studentId, planExamDO.getPlanId());
                    log.info("完成学案成功, planId: {}", planExamDO.getPlanId());
                }

                List<WordMasteryLogDO> wordMasteryLogDOS = new ArrayList<>(unmemorizedWordIds.stream().map(wordId -> WordMasteryLogDO.builder()
                        .wordId(wordId)
                        .studentId(studentId)
                        .reviewCount(1)
                        .memoryStrength(-0.5)
                        .update_time(LocalDateTime.now())
                        .build()
                ).toList());

                wordMasteryLogDOS.addAll(memorizedWordIds.stream().map(wordId -> WordMasteryLogDO.builder()
                        .wordId(wordId)
                        .studentId(studentId)
                        .reviewCount(1)
                        .memoryStrength(0.5)
                        .update_time(LocalDateTime.now())
                        .build()
                ).toList());
                wordMasteryLogDOMapper.batchUpdateStudentMastery(wordMasteryLogDOS);
                log.info("更新单词掌握记录成功");

                int updateStudentExamWordsFinished = studentExamWordsDOMapper.updateStudentExamWordsFinished(studentId, examWordsId);
                if (updateStudentExamWordsFinished != 1) {
                    examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "更新学生考试为结束时失败");
                }

                boolean delete = new File(ansSheetPath).delete();
                if (delete) {
                    log.info("删除文件成功：{}", ansSheetPath);
                }
            } catch (Exception e) {
                log.error("识别考试失败 {}", e.getMessage());
                examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDO.getId(), e.getMessage());
            }
        }
    }

    private void judgeExamActualGrade(ExamWordsJudgeResultDO wordsJudgeResultDO, ExamWordsDO examWordsDO) {
        List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectVocabularyBankDOListByIds(examWordsDO.getWordIds());
        Map<Integer, List<VocabularyBankDO>> unitId2Words = vocabularyBankDOS.stream().collect(Collectors.groupingBy(VocabularyBankDO::getUnitId));

        List<GradeUnitDO> gradeUnitDOS = gradeUnitDOMapper.selectByUnitIds(unitId2Words.keySet().stream().toList());

        // unitId -> gradeId
        Map<Integer, Integer> unitId2GradeId = gradeUnitDOS.stream()
                .collect(Collectors.toMap(
                        GradeUnitDO::getUnitId,
                        GradeUnitDO::getGradeId,
                        (existing, replacement) -> existing // 如果重复，保留第一个（或根据业务决定）
                ));
        // gradeId -> List<VocabularyBankDO>
        Map<Integer, List<VocabularyBankDO>> gradeId2Words = vocabularyBankDOS.stream()
                .filter(vocab -> vocab.getUnitId() != null)
                .filter(vocab -> unitId2GradeId.containsKey(vocab.getUnitId()))
                .collect(Collectors.groupingBy(
                        vocab -> unitId2GradeId.get(vocab.getUnitId())
                ));

        // 核心数据结构：GradeId -> List<WordId> (试卷里包含的每个年级的单词有哪些)
        Map<Integer, Set<Integer>> gradeId2WordIdsMap = new HashMap<>();

        for (VocabularyBankDO vocab : vocabularyBankDOS) {
            if (vocab.getUnitId() != null && unitId2GradeId.containsKey(vocab.getUnitId())) {
                Integer gradeId = unitId2GradeId.get(vocab.getUnitId());
                gradeId2WordIdsMap.computeIfAbsent(gradeId, k -> new HashSet<>()).add(vocab.getId());
            }
        }

        // 统计各区域正确率 (Calculate Accuracy per Zone)
        // 锚点年级 (G_test)，即这张卷子是按哪个年级生成的
        Integer anchorGrade = examWordsDO.getGradeId();
        // 挑战区
        int zoneA = ExamWordsConstant.getZoneA(anchorGrade);
        ZoneStats zoneAStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneA);
        // 当前核心
        int zoneB = ExamWordsConstant.getZoneB(anchorGrade);
        ZoneStats zoneBStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneB);
        // 回溯 Level 1
        int zoneC = ExamWordsConstant.getZoneC(anchorGrade);
        ZoneStats zoneCStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneC);
        // 回溯 Level 2
        int zoneD = ExamWordsConstant.getZoneD(anchorGrade);
        ZoneStats zoneDStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneD);
        // 回溯 Level 3
        int zoneE = ExamWordsConstant.getZoneE(anchorGrade);
        ZoneStats zoneEStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneE);
        // 地基兜底
        int zoneF = ExamWordsConstant.getZoneF(anchorGrade);
        ZoneStats zoneFStats = calculateZoneStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneF);
        // 地基区
        ZoneStats zoneFoundationStats = calculateFoundationStats(gradeId2WordIdsMap, wordsJudgeResultDO, zoneD);

        DiagnosisResult diagnosisResult = diagnoseStudentLevel(anchorGrade, zoneAStats, zoneBStats, zoneCStats, zoneDStats, zoneFoundationStats);

        studentDOMapper.updateStudentActualGradeId(wordsJudgeResultDO.getStudentId(), diagnosisResult.getDeterminedLevel());
        log.info("判断结果：{}", diagnosisResult);
        wordsJudgeResultDO.setMsg(diagnosisResult.getMessage());
    }

    /**
     * 计算特定年级的统计数据
     */
    private ZoneStats calculateZoneStats(Map<Integer, Set<Integer>> map, ExamWordsJudgeResultDO result, Integer targetGrade) {
        Set<Integer> totalWords = map.getOrDefault(targetGrade, Collections.emptySet());
        if (totalWords.isEmpty()) return new ZoneStats(targetGrade, 0, 0, 0.0);

        long correctCount = result.getCorrectWordIds().stream().filter(totalWords::contains).count();
        double accuracy = (double) correctCount / totalWords.size();

        return new ZoneStats(targetGrade, totalWords.size(), (int)correctCount, accuracy);
    }

    /**
     * 计算地基区（所有低于某年级）的统计数据
     */
    private ZoneStats calculateFoundationStats(Map<Integer, Set<Integer>> map, ExamWordsJudgeResultDO result, Integer maxGradeThreshold) {
        Set<Integer> foundationWords = new HashSet<>();
        map.forEach((grade, words) -> {
            if (grade <= maxGradeThreshold) {
                foundationWords.addAll(words);
            }
        });

        if (foundationWords.isEmpty()) return new ZoneStats(-1, 0, 0, 0.0);

        long correctCount = result.getCorrectWordIds().stream().filter(foundationWords::contains).count();
        double accuracy = (double) correctCount / foundationWords.size();

        return new ZoneStats(-1, foundationWords.size(), (int)correctCount, accuracy);
    }

    /**
     * 核心诊断逻辑矩阵
     */
    private DiagnosisResult diagnoseStudentLevel(Integer anchorGrade, ZoneStats zoneA, ZoneStats zoneB, ZoneStats zoneC, ZoneStats zoneD, ZoneStats zoneFoundation) {

        // --- 场景 1: 熔断机制 (Meltdown) ---
        // 针对 G8/G9 等高年级，如果地基区（G4/G5及以下）错误率极高
        // 阈值设为 20% (即 Zone F 正确率 < 20%)
        if (zoneFoundation.getTotalCount() > 0 && zoneFoundation.getAccuracy() < 0.20 && anchorGrade >= 7) {
            return new DiagnosisResult(
                    4, // 强制下沉到 G4 (根据文档逻辑，熔断通常锚定 G4)
                    ActionType.TRIGGER_RETEST,
                    "严重预警！检测到您的基础词汇（" + ExamWordsConstant.getGradeName(anchorGrade - 3) + "及以下）存在大面积坍塌。系统已为您启动【基础词汇专项排查】，请勿担心，这是为了更好地起跳。"
            );
        }

        // --- 场景 2: 进阶/跳级 (Upgrade) ---
        // 必须满足：当前年级(B) > 80% 且 挑战年级(A) > 60% (如果有题的话)
        boolean canUpgrade = zoneB.getAccuracy() >= 0.8 && (zoneA.getTotalCount() == 0 || zoneA.getAccuracy() >= 0.6);
        if (canUpgrade) {
            return new DiagnosisResult(
                    Math.max(anchorGrade + 1, ExamWordsConstant.GRADE_8),
                    ActionType.UPGRADE,
                    "恭喜！您对" + ExamWordsConstant.getGradeName(anchorGrade) + " 的掌握非常扎实，且具备挑战" + ExamWordsConstant.getGradeName(Math.max(anchorGrade + 1, ExamWordsConstant.GRADE_8)) + " 的潜力。系统将为您解锁更高阶词库。"
            );
        }

        // --- 场景 3: 正常回溯诊断 (Standard Diagnosis) ---

        // 3.1 当前年级崩盘 (Zone B < 60%)
        if (zoneB.getAccuracy() < 0.6) {

            // 检查上一级 (Zone C)
            if (zoneC.getAccuracy() >= 0.8) {
                // "中考空心病" / "基础扎实但新课未动"
                // 定级：保持在当前年级，但侧重复习
                return new DiagnosisResult(
                        anchorGrade,
                        ActionType.STAY_AND_REVIEW,
                        "基础尚可(" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + "掌握较好)，但" + ExamWordsConstant.getGradeName(anchorGrade) + " 新词汇存在较大缺口。建议：重点攻克本年级核心动词。"
                );
            } else if (zoneC.getAccuracy() >= 0.6) {
                // Zone C 勉强及格，Zone B 不及格 -> 降一级
                return new DiagnosisResult(
                        Math.max(anchorGrade - 1, ExamWordsConstant.GRADE_1),
                        ActionType.DOWNGRADE,
                        "检测到" + ExamWordsConstant.getGradeName(anchorGrade) + " 学习吃力，且" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + " 存在模糊点。建议降级回溯，巩固 " + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + "。"
                );
            } else {
                // Zone C 也崩了 (< 60%) -> 连降两级或更多
                // 检查 Zone D
                if (zoneD.getTotalCount() > 0 && zoneD.getAccuracy() >= 0.6) {
                    return new DiagnosisResult(
                            Math.max(anchorGrade - 2, ExamWordsConstant.GRADE_1),
                            ActionType.DOWNGRADE,
                            "您的" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + " 和 " + ExamWordsConstant.getGradeName(anchorGrade) + " 均存在脱节。建议从 " + ExamWordsConstant.getGradeName(zoneD.getGradeId()) + " 开始系统补漏。"
                    );
                } else {
                    // 彻底崩盘，可能需要熔断，或者定级到更低
                    return new DiagnosisResult(
                            Math.max(ExamWordsConstant.GRADE_1, anchorGrade - 3),
                            ActionType.DOWNGRADE,
                            "基础薄弱，建议暂停当前进度，从低年级核心高频词重新开始。"
                    );
                }
            }
        }

        // 3.2 正常达标 (Zone B 60% - 80%)
        return new DiagnosisResult(
                anchorGrade,
                ActionType.PASS,
                "当前年级达标。建议继续保持，并尝试在阅读中增加长难句练习。"
        );
    }

    @Override
    public List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize, Integer classId, Integer gradeId, String studentName) {

        List<Integer> studentIds = new ArrayList<>();
        if (classId != null) {
            studentIds.addAll(studentDOMapper.selectStudentDOListByClassId(classId).stream().map(StudentDO::getId).toList());
        }
        if (gradeId != null) {
            studentIds.addAll(studentDOMapper.selectStudentDOListByGradeId(gradeId).stream().map(StudentDO::getId).toList());
        }
        if (!studentName.isEmpty()) {
            studentIds.addAll(studentDOMapper.selectStudentDOListByName(studentName).stream().map(StudentDO::getId).toList());
        }

        if (studentIds.isEmpty()) {
            return examWordsJudgeResultDOMapper.selectByPage((page - 1) * pageSize, pageSize);
        } else {
            return examWordsJudgeResultDOMapper.selectByPageAndStudentIds((page - 1) * pageSize, page * pageSize, studentIds);
        }
    }

    @Override
    public Integer getExamWordsJudgeResultCount() {
        return examWordsJudgeResultDOMapper.selectCount();
    }

    @Override
    public Integer getExamUnfinishedCount() {
        return examWordsJudgeResultDOMapper.selectUnfinishedCount();
    }

    @Override
    public ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer id) {
        return examWordsJudgeResultDOMapper.selectDetailById(id);
    }

    @Override
    public List<ExamWordsJudgeResultDO> getStudentExamWordsResultList(Integer studentId) {
        return examWordsJudgeResultDOMapper.selectByStudentId(studentId);
    }
}
