package com.seer.teach.teacher.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.seer.teach.common.enums.llm.OcrLableEnum;
import com.seer.teach.common.service.CommonFileService;
import com.seer.teach.teacher.common.llm.resp.BboxLocationResult;
import com.seer.teach.teacher.common.llm.resp.BboxResult;
import com.seer.teach.teacher.common.llm.resp.ProcessUploadResult;
import com.seer.teach.teacher.common.resp.BboxLocationResp;
import com.seer.teach.teacher.common.resp.BboxResp;
import com.seer.teach.teacher.common.utils.TeacherUtils;
import com.seer.teach.teacher.service.CorrectService;
import com.seer.teach.teacher.service.LlmRequestService;
import com.seer.teach.teacher.service.prompt.impl.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

/**
 * CorrectServiceImpl 是 CorrectService 接口的一个实现类，用于处理教师相关 OCR 及题目完整性校验逻辑。
 * 主要功能包括：上传文件进行 OCR 识别、过滤有效题目区域、并发请求处理上传、验证题目完整性，并将结果封装返回。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class CorrectServiceImpl implements CorrectService {

    /**
     * LLM 请求服务接口，负责调用大模型执行 OCR 和图像处理任务。
     */
    private final LlmRequestService llmRequestService;

    /**
     * 异步执行器，用于并发执行多个耗时操作（如图像处理）以提升性能。
     */
    private final Executor llmExecutor;

    /**
     * 题目完整性校验组件，用于分析并筛选出结构完整的题目内容。
     */
    private final ValidateQuestionIntegrity validateQuestionIntegrity;

    private final CorrectQuestionService correctQuestionService;

    private final CorrectKnowledgePointService correctKnowledgePointService;

    private final CorrectLostPointService correctLostPointService;

    private final CorrectNessService correctNessService;

    /**
     * 公共文件服务，提供 AI 批改相关的图片上传能力。
     */
    private final CommonFileService commonFileService;

    /**
     * 对传入的图片文件执行 OCR 处理流程，提取其中的有效题目信息并完成后续处理与校验。
     *
     * @param file 用户上传的原始图片文件，需为支持的图像格式。
     * @return OcrResp 包含原图 URL 和经过处理后有效的题目位置及对应图片地址列表。
     */
    @Override
    public BboxResp ocr(MultipartFile file) {
        // 获取当前登录用户的 ID
        Integer userId = StpUtil.getLoginIdAsInt();
        BboxResult ocr = bBox(file, userId);
        List<BboxLocationResult> ocrResults = filterLabel(ocr);
        List<BboxLocationResult> validated = validateQuestionIntegrity.validateQuestionIntegrity(userId, ocrResults);
        ConcurrentHashMap<Integer, String> contextMap = getContext(validated, userId, ocr);
        ConcurrentHashMap<Integer, ProcessUploadResult> processUploadMap = getIntegerProcessUploadResult(validated, contextMap, userId);
        ConcurrentHashMap<Integer, String> ocrTexts = ocr(validated, userId);
        ConcurrentHashMap<Integer, String> knowPoints = getKnowPoints(validated, processUploadMap, ocrTexts, userId);
        ConcurrentHashMap<Integer, String> lostPoints = getLostPoints(validated, ocrTexts, processUploadMap, userId);
        ConcurrentHashMap<Integer, String> nessMap = getNess(validated, ocrTexts, processUploadMap, userId);
        ConcurrentHashMap<Integer, Boolean> correctMap = getCorrectMap(validated, processUploadMap, ocrTexts, knowPoints, lostPoints, nessMap, userId);
        List<BboxLocationResp> locations = uploadFile(validated, correctMap, userId);
        return new BboxResp(ocr.getInputImgUrl(), locations);
    }

    /**
     * 将裁剪后的题目图片上传至文件服务器，并构建响应对象列表。
     *
     * @param validated   已通过完整性校验的题目区域数据列表。
     * @param correctMap  每个题目的批改状态映射表。
     * @param userId      当前用户ID。
     * @return 返回包含每个题目位置信息和图片URL的响应对象列表。
     */
    private List<BboxLocationResp> uploadFile(List<BboxLocationResult> validated, ConcurrentHashMap<Integer, Boolean> correctMap, Integer userId) {
        List<BboxLocationResp> locations = Collections.synchronizedList(new ArrayList<>());
        CompletableFuture.allOf(
                validated.stream()
                        .map(ocrResult -> CompletableFuture.runAsync(() -> {
                            try {
                                Boolean correct = correctMap.get(ocrResult.getOrder());
                                MultipartFile uploadFile = TeacherUtils.base64ToMultipart(ocrResult.getCropedImg());
                                // 上传图片到文件服务器
                                String url = commonFileService.uploadAiCorrectImage(userId, uploadFile);
                                BboxLocationResp location = new BboxLocationResp(ocrResult.getOrder(), correct, ocrResult.getBbox(), url);
                                locations.add(location);
                            } catch (Exception e) {
                                log.error("用户[{}] 上传图片失败 label={}", userId, ocrResult.getLabel(), e);
                            }
                        }, llmExecutor))
                        .toArray(CompletableFuture[]::new)
        ).join();
        return locations;
    }

    /**
     * 并发获取每道题是否正确判断的结果。
     *
     * @param validated         已通过完整性校验的题目区域数据列表。
     * @param processUploadMap  图像上下文处理结果映射表。
     * @param ocrTexts          各题OCR文本内容映射表。
     * @param knowPoints        各题知识点映射表。
     * @param lostPoints        各题失分点映射表。
     * @param nessMap           各题评分标准映射表。
     * @param userId            当前用户ID。
     * @return 返回各题是否正确的布尔值映射表。
     */
    private ConcurrentHashMap<Integer, Boolean> getCorrectMap(List<BboxLocationResult> validated, ConcurrentHashMap<Integer, ProcessUploadResult> processUploadMap, ConcurrentHashMap<Integer, String> ocrTexts, ConcurrentHashMap<Integer, String> knowPoints, ConcurrentHashMap<Integer, String> lostPoints, ConcurrentHashMap<Integer, String> nessMap, Integer userId) {
        ConcurrentHashMap<Integer, Boolean> correctMap = new ConcurrentHashMap<>();
        CompletableFuture.allOf(
                validated.stream()
                        .map(ocrResult -> CompletableFuture.runAsync(() -> {
                            try {
                                ProcessUploadResult processResult = processUploadMap.get(ocrResult.getOrder());
                                String ocrText = ocrTexts.get(ocrResult.getOrder());
                                String knowPoint = knowPoints.get(ocrResult.getOrder());
                                String lostPoint = lostPoints.get(ocrResult.getOrder());
                                String ness = nessMap.get(ocrResult.getOrder());
                                Boolean correct = correctQuestionService.correct(userId, processResult, ocrText, knowPoint, lostPoint, ness);
                                correctMap.put(ocrResult.getOrder(), correct);
                            } catch (Exception e) {
                                log.error("用户[{}] 批改步骤失败 label={}", userId, ocrResult.getLabel(), e);
                            }
                        }, llmExecutor))
                        .toArray(CompletableFuture[]::new)
        ).join();
        return correctMap;
    }

    /**
     * 并发获取每道题的评分标准描述。
     *
     * @param validated         已通过完整性校验的题目区域数据列表。
     * @param ocrTexts          各题OCR文本内容映射表。
     * @param processUploadMap  图像上下文处理结果映射表。
     * @param userId            当前用户ID。
     * @return 返回各题评分标准字符串映射表。
     */
    private ConcurrentHashMap<Integer, String> getNess(List<BboxLocationResult> validated, ConcurrentHashMap<Integer, String> ocrTexts, ConcurrentHashMap<Integer, ProcessUploadResult> processUploadMap, Integer userId) {
        ConcurrentHashMap<Integer, String> nessMap = new ConcurrentHashMap<>();
        CompletableFuture.allOf(
                validated.stream()
                        .map(ocrResult -> CompletableFuture.runAsync(() -> {
                            try {
                                String ocrText = ocrTexts.get(ocrResult.getOrder());
                                ProcessUploadResult processResult = processUploadMap.get(ocrResult.getOrder());
                                String ness = correctNessService.correctNess(userId, ocrText, processResult);
                                nessMap.put(ocrResult.getOrder(), ness);
                            } catch (Exception e) {
                                log.error("用户[{}] 获取评分标准失败 label={}", userId, ocrResult.getLabel(), e);
                            }
                        }, llmExecutor))
                        .toArray(CompletableFuture[]::new)
        ).join();
        return nessMap;
    }

    /**
     * 并发获取每道题的失分点描述。
     *
     * @param validated         已通过完整性校验的题目区域数据列表。
     * @param ocrTexts          各题OCR文本内容映射表。
     * @param processUploadMap  图像上下文处理结果映射表。
     * @param userId            当前用户ID。
     * @return 返回各题失分点字符串映射表。
     */
    private ConcurrentHashMap<Integer, String> getLostPoints(List<BboxLocationResult> validated, ConcurrentHashMap<Integer, String> ocrTexts, ConcurrentHashMap<Integer, ProcessUploadResult> processUploadMap, Integer userId) {
        ConcurrentHashMap<Integer, String> lostPoints = new ConcurrentHashMap<>();
        CompletableFuture.allOf(
                validated.stream()
                        .map(ocrResult -> CompletableFuture.runAsync(() -> {
                            try {
                                String ocrText = ocrTexts.get(ocrResult.getOrder());
                                ProcessUploadResult processResult = processUploadMap.get(ocrResult.getOrder());
                                String lostPoint = correctLostPointService.lostPoint(userId, ocrText, processResult);
                                lostPoints.put(ocrResult.getOrder(), lostPoint);
                            } catch (Exception e) {
                                log.error("用户[{}] 获取失分点失败 失败 label={}", userId, ocrResult.getLabel(), e);
                            }
                        }, llmExecutor))
                        .toArray(CompletableFuture[]::new)
        ).join();
        return lostPoints;
    }

    /**
     * 并发获取每道题的知识点描述。
     *
     * @param validated         已通过完整性校验的题目区域数据列表。
     * @param processUploadMap  图像上下文处理结果映射表。
     * @param ocrTexts          各题OCR文本内容映射表。
     * @param userId            当前用户ID。
     * @return 返回各题知识点字符串映射表。
     */
    private ConcurrentHashMap<Integer, String> getKnowPoints(List<BboxLocationResult> validated, ConcurrentHashMap<Integer, ProcessUploadResult> processUploadMap, ConcurrentHashMap<Integer, String> ocrTexts, Integer userId) {
        ConcurrentHashMap<Integer, String> knowPoints = new ConcurrentHashMap<>();
        CompletableFuture.allOf(
                validated.stream()
                        .map(ocrResult -> CompletableFuture.runAsync(() -> {
                            try {
                                ProcessUploadResult processResult = processUploadMap.get(ocrResult.getOrder());
                                String ocrText = ocrTexts.get(ocrResult.getOrder());
                                String knowPoint = correctKnowledgePointService.knowledgePoint(userId, ocrText, processResult);
                                knowPoints.put(ocrResult.getOrder(), knowPoint);
                            } catch (Exception e) {
                                log.error("用户[{}] 上传图片失败 label={}", userId, ocrResult.getLabel(), e);
                            }
                        }, llmExecutor))
                        .toArray(CompletableFuture[]::new)
        ).join();
        return knowPoints;
    }

    /**
     * 并发对每个题目区域进行OCR识别，提取文字内容。
     *
     * @param validated 已通过完整性校验的题目区域数据列表。
     * @param userId    当前用户ID。
     * @return 返回各题OCR识别结果字符串映射表。
     */
    private ConcurrentHashMap<Integer, String> ocr(List<BboxLocationResult> validated, Integer userId) {
        ConcurrentHashMap<Integer, String> ocrTexts = new ConcurrentHashMap<>();
        CompletableFuture.allOf(
                validated.stream()
                        .map(ocrResult -> CompletableFuture.runAsync(() -> {
                            try {
                                String ocrText = llmRequestService.requestOcrWithBase64(userId, ocrResult.getCropedImg());
                                ocrTexts.put(ocrResult.getOrder(), ocrText);
                            } catch (Exception e) {
                                log.error("用户[{}] 上传图片失败 label={}", userId, ocrResult.getLabel(), e);
                            }
                        }, llmExecutor))
                        .toArray(CompletableFuture[]::new)
        ).join();
        return ocrTexts;
    }

    /**
     * 并发向LLM发起请求，处理单个题目的上下文信息。
     *
     * @param validated   已通过完整性校验的题目区域数据列表。
     * @param contextMap  存储上下文信息的映射表。
     * @param userId      当前用户ID。
     * @return 返回处理后的上下文结果映射表。
     */
    private ConcurrentHashMap<Integer, ProcessUploadResult> getIntegerProcessUploadResult(List<BboxLocationResult> validated, ConcurrentHashMap<Integer, String> contextMap, Integer userId) {
        ConcurrentHashMap<Integer, ProcessUploadResult> processUploadMap = new ConcurrentHashMap<>();
        CompletableFuture.allOf(
                validated.stream()
                        .map(ocrResult -> CompletableFuture.runAsync(() -> {
                            try {
                                String context = contextMap.get(ocrResult.getOrder());
                                ProcessUploadResult processResult = llmRequestService.requestProcessUpload(userId, context);
                                processUploadMap.put(ocrResult.getOrder(), processResult);
                            } catch (Exception e) {
                                log.error("用户[{}] 上传图片失败 label={}", userId, ocrResult.getLabel(), e);
                            }
                        }, llmExecutor))
                        .toArray(CompletableFuture[]::new)
        ).join();
        return processUploadMap;
    }

    /**
     * 并发获取每个题目的上下文信息。
     *
     * @param validated 已通过完整性校验的题目区域数据列表。
     * @param userId    当前用户ID。
     * @param ocr       原始OCR识别结果对象。
     * @return 返回每个题目的上下文字符串映射表。
     */
    private ConcurrentHashMap<Integer, String> getContext(
            List<BboxLocationResult> validated,
            Integer userId,
            BboxResult ocr) {
        ConcurrentHashMap<Integer, String> contextMap = new ConcurrentHashMap<>();
        CompletableFuture<?>[] futures = validated.stream()
                .map(ocrResult ->
                        CompletableFuture.supplyAsync(() -> {
                                    try {
                                        return llmRequestService.requestContextAboutSingleQuestion(
                                                userId,
                                                ocr.getInputImgUrl(),
                                                ocrResult.getBbox()
                                        );
                                    } catch (Exception e) {
                                        log.error("用户[{}] 上下文生成失败 label={}", userId, ocrResult.getLabel(), e);
                                        return null;
                                    }
                                }, llmExecutor)
                                .thenAccept(context -> {
                                    if (context != null) {
                                        contextMap.put(ocrResult.getOrder(), context);
                                    }
                                })
                ).toArray(CompletableFuture[]::new);
        CompletableFuture.allOf(futures).join();
        return contextMap;
    }

    /**
     * 根据标签类型过滤出有效的OCR识别结果，仅保留题目正文、公式或答题框等关键区域。
     *
     * @param ocr 完整的OCR识别结果对象。
     * @return 过滤后仅包含有效题目标签的数据列表。
     */
    private static List<BboxLocationResult> filterLabel(BboxResult ocr) {
        return ocr.getOcrResults().stream()
                .filter(ocrResult -> ocrResult.getLabel().equals(OcrLableEnum.TEXT.getCode())
                        || ocrResult.getLabel().equals(OcrLableEnum.DISPLAY_FORMULA.getCode())
                        || ocrResult.getLabel().equals(OcrLableEnum.QUESTION_BOX.getCode()))
                .collect(Collectors.toList());
    }

    /**
     * 使用LLM服务对输入图片进行带边框信息的OCR识别。
     *
     * @param file   用户上传的图片文件。
     * @param userId 当前用户ID。
     * @return 返回包含所有识别结果及其边界框的对象。
     */
    private BboxResult bBox(MultipartFile file, Integer userId) {
        log.info("用户[{}] 开始OCR", userId);
        // 调用 LLM 进行带边框信息的 Bbox 识别
        return llmRequestService.requestBbox(userId, file);
    }

}
