package com.ycwl.basic.puzzle.service.impl;

import cn.hutool.json.JSONUtil;
import com.ycwl.basic.biz.FaceStatusManager;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import com.ycwl.basic.puzzle.dto.PuzzleGenerateRequest;
import com.ycwl.basic.puzzle.dto.PuzzleGenerateResponse;
import com.ycwl.basic.puzzle.edge.task.PuzzleEdgeRenderTaskService;
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity;
import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity;
import com.ycwl.basic.puzzle.fill.FillResult;
import com.ycwl.basic.puzzle.fill.PuzzleElementFillEngine;
import com.ycwl.basic.puzzle.mapper.PuzzleGenerationRecordMapper;
import com.ycwl.basic.puzzle.repository.PuzzleRepository;
import com.ycwl.basic.puzzle.service.IPuzzleGenerateService;
import com.ycwl.basic.puzzle.util.PuzzleDuplicationDetector;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.pc.processor.PuzzleRelationProcessor;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.utils.WxMpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.nio.file.Files;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * 拼图图片生成服务实现
 *
 * @author Claude
 * @since 2025-01-17
 */
@Slf4j
@Service
public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {

    private final PuzzleRepository puzzleRepository;
    private final PuzzleGenerationRecordMapper recordMapper;
    private final PuzzleElementFillEngine fillEngine;
    private final ScenicRepository scenicRepository;
    private final PuzzleDuplicationDetector duplicationDetector;
    private final PuzzleEdgeRenderTaskService puzzleEdgeRenderTaskService;
    private final FaceStatusManager faceStatusManager;
    private final PuzzleRelationProcessor puzzleRelationProcessor;

    public PuzzleGenerateServiceImpl(
            PuzzleRepository puzzleRepository,
            PuzzleGenerationRecordMapper recordMapper,
            @Lazy PuzzleElementFillEngine fillEngine,
            @Lazy ScenicRepository scenicRepository,
            @Lazy PuzzleDuplicationDetector duplicationDetector,
            PuzzleEdgeRenderTaskService puzzleEdgeRenderTaskService,
            @Lazy FaceStatusManager faceStatusManager,
            @Lazy PuzzleRelationProcessor puzzleRelationProcessor) {
        this.puzzleRepository = puzzleRepository;
        this.recordMapper = recordMapper;
        this.fillEngine = fillEngine;
        this.scenicRepository = scenicRepository;
        this.duplicationDetector = duplicationDetector;
        this.puzzleEdgeRenderTaskService = puzzleEdgeRenderTaskService;
        this.faceStatusManager = faceStatusManager;
        this.puzzleRelationProcessor = puzzleRelationProcessor;
    }

    @Override
    public PuzzleGenerateResponse generate(PuzzleGenerateRequest request) {
        // 默认使用同步模式
        return generateSync(request);
    }

    @Override
    public PuzzleGenerateResponse generateSync(PuzzleGenerateRequest request) {
        long startTime = System.currentTimeMillis();
        log.info("开始同步生成拼图（边缘渲染模式）: templateCode={}, userId={}, faceId={}",
                request.getTemplateCode(), request.getUserId(), request.getFaceId());

        // 1. 参数校验
        validateRequest(request);

        // 2. 查询模板（使用缓存）
        PuzzleTemplateEntity template = puzzleRepository.getTemplateByCode(request.getTemplateCode());
        if (template == null) {
            throw new IllegalArgumentException("模板不存在: " + request.getTemplateCode());
        }
        if (template.getStatus() != 1) {
            throw new IllegalArgumentException("模板已禁用: " + request.getTemplateCode());
        }
        Long resolvedScenicId = resolveScenicId(template, request.getScenicId());

        // 2.5 素材版本缓存检查（减少数据库查询）
        // 如果缓存存在，说明自上次成功生成后素材没有变化，可以直接复用历史记录
        if (request.getFaceId() != null && !faceStatusManager.isPuzzleSourceChanged(request.getFaceId(), template.getId(), 0)) {
            PuzzleGenerationRecordEntity cachedRecord = recordMapper.findLatestSuccessByFaceAndTemplate(
                    request.getFaceId(), template.getId());
            if (cachedRecord != null) {
                long duration = System.currentTimeMillis() - startTime;
                log.info("素材版本缓存命中，复用历史记录: faceId={}, templateId={}, recordId={}, imageUrl={}, duration={}ms",
                        request.getFaceId(), template.getId(), cachedRecord.getId(),
                        cachedRecord.getResultImageUrl(), duration);
                return PuzzleGenerateResponse.success(
                        cachedRecord.getResultImageUrl(),
                        cachedRecord.getResultFileSize(),
                        cachedRecord.getResultWidth(),
                        cachedRecord.getResultHeight(),
                        (int) duration,
                        cachedRecord.getId(),
                        true,
                        cachedRecord.getId()
                );
            }
            // 缓存存在但记录被删除，继续执行正常流程
            log.debug("素材版本缓存存在但历史记录不存在，继续正常生成: faceId={}, templateId={}",
                    request.getFaceId(), template.getId());
        }

        // 3. 查询并排序元素
        List<PuzzleElementEntity> elements = puzzleRepository.getElementsByTemplateId(template.getId());
        if (elements.isEmpty()) {
            throw new IllegalArgumentException("模板没有配置元素: " + request.getTemplateCode());
        }
        elements.sort(Comparator.comparing(PuzzleElementEntity::getZIndex,
                Comparator.nullsFirst(Comparator.naturalOrder())));

        // 4. 构建dynamicData
        Map<String, String> finalDynamicData = buildDynamicData(template, request, resolvedScenicId, elements);

        // 5. 重复图片检测（可能抛出DuplicateImageException）
        duplicationDetector.detectDuplicateImages(finalDynamicData, elements);

        // 6. 内容去重检测
        String contentHash = duplicationDetector.calculateContentHash(finalDynamicData);
        PuzzleGenerationRecordEntity duplicateRecord = duplicationDetector.findDuplicateRecord(
                template.getId(), contentHash, resolvedScenicId
        );
        if (duplicateRecord != null) {
            if (duplicateRecord.getStatus() == 1) {
                // 已有成功记录，直接复用
                long duration = System.currentTimeMillis() - startTime;
                log.info("检测到重复内容，复用历史记录: recordId={}, imageUrl={}, duration={}ms",
                        duplicateRecord.getId(), duplicateRecord.getResultImageUrl(), duration);
                // 标记素材版本缓存
                if (request.getFaceId() != null) {
                    faceStatusManager.markPuzzleSourceVersion(request.getFaceId(), template.getId(), 0);
                }
                return PuzzleGenerateResponse.success(
                        duplicateRecord.getResultImageUrl(),
                        duplicateRecord.getResultFileSize(),
                        duplicateRecord.getResultWidth(),
                        duplicateRecord.getResultHeight(),
                        (int) duration,
                        duplicateRecord.getId(),
                        true,
                        duplicateRecord.getId()
                );
            } else if (duplicateRecord.getStatus() == 0) {
                // 相同内容正在生成中，等待完成后复用
                log.info("检测到相同内容正在生成中，等待完成: recordId={}", duplicateRecord.getId());
                PuzzleGenerationRecordEntity completedRecord = waitForRecordCompletion(duplicateRecord.getId(), 30_000);
                if (completedRecord != null && completedRecord.getStatus() == 1) {
                    long duration = System.currentTimeMillis() - startTime;
                    log.info("等待生成中记录完成，复用结果: recordId={}, imageUrl={}, duration={}ms",
                            completedRecord.getId(), completedRecord.getResultImageUrl(), duration);
                    if (request.getFaceId() != null) {
                        faceStatusManager.markPuzzleSourceVersion(request.getFaceId(), template.getId(), 0);
                    }
                    return PuzzleGenerateResponse.success(
                            completedRecord.getResultImageUrl(),
                            completedRecord.getResultFileSize(),
                            completedRecord.getResultWidth(),
                            completedRecord.getResultHeight(),
                            (int) duration,
                            completedRecord.getId(),
                            true,
                            completedRecord.getId()
                    );
                }
                // 超时或失败，兜底创建新记录
                log.warn("等待生成中记录超时或失败，创建新记录: originalRecordId={}", duplicateRecord.getId());
            }
        }

        // 7. 创建生成记录
        PuzzleGenerationRecordEntity record = createRecord(template, request, resolvedScenicId);
        record.setContentHash(contentHash);
        recordMapper.insert(record);

        // 清除生成记录缓存（新记录插入后列表和数量都会变化）
        puzzleRepository.clearRecordCacheByFace(request.getFaceId());

        // 8. 创建边缘渲染任务并等待完成
        Long taskId = puzzleEdgeRenderTaskService.createRenderTask(
                record,
                template,
                elements,
                finalDynamicData,
                request.getOutputFormat(),
                request.getQuality()
        );
        puzzleEdgeRenderTaskService.registerWait(taskId);

        log.info("同步拼图任务已提交边缘渲染，等待完成: recordId={}, taskId={}", record.getId(), taskId);

        // 9. 等待任务完成（30秒超时）
        PuzzleEdgeRenderTaskService.TaskWaitResult waitResult =
                puzzleEdgeRenderTaskService.waitForTask(taskId, 30_000);

        long duration = System.currentTimeMillis() - startTime;

        if (waitResult.isSuccess()) {
            log.info("同步拼图边缘渲染完成: recordId={}, taskId={}, imageUrl={}, duration={}ms",
                    record.getId(), taskId, waitResult.getImageUrl(), duration);

            // 标记素材版本缓存（成功生成后）
            if (request.getFaceId() != null) {
                faceStatusManager.markPuzzleSourceVersion(request.getFaceId(), template.getId(), 0);
            }

            // 创建member_puzzle关联记录
            puzzleRelationProcessor.createPuzzleRelation(
                    request.getUserId(),
                    resolvedScenicId,
                    request.getFaceId(),
                    record.getId()
            );

            // 重新查询记录获取完整信息（边缘渲染回调已更新）
            PuzzleGenerationRecordEntity updatedRecord = recordMapper.getById(record.getId());
            if (updatedRecord != null && updatedRecord.getResultImageUrl() != null) {
                return PuzzleGenerateResponse.success(
                        updatedRecord.getResultImageUrl(),
                        updatedRecord.getResultFileSize(),
                        updatedRecord.getResultWidth(),
                        updatedRecord.getResultHeight(),
                        (int) duration,
                        updatedRecord.getId()
                );
            }

            // 回调可能还未完全写入，使用等待结果中的URL
            return PuzzleGenerateResponse.success(
                    waitResult.getImageUrl(),
                    null,
                    template.getCanvasWidth(),
                    template.getCanvasHeight(),
                    (int) duration,
                    record.getId()
            );
        } else {
            log.error("同步拼图边缘渲染失败: recordId={}, taskId={}, error={}, duration={}ms",
                    record.getId(), taskId, waitResult.getErrorMessage(), duration);
            throw new RuntimeException("拼图生成失败: " + waitResult.getErrorMessage());
        }
    }

    @Override
    public Long generateAsync(PuzzleGenerateRequest request) {
        long startTime = System.currentTimeMillis();
        log.info("开始创建异步拼图边缘渲染任务: templateCode={}, userId={}, faceId={}",
                request.getTemplateCode(), request.getUserId(), request.getFaceId());

        // 1. 参数校验
        validateRequest(request);

        // 2. 查询模板（使用缓存）
        PuzzleTemplateEntity template = puzzleRepository.getTemplateByCode(request.getTemplateCode());
        if (template == null) {
            throw new IllegalArgumentException("模板不存在: " + request.getTemplateCode());
        }
        if (template.getStatus() != 1) {
            throw new IllegalArgumentException("模板已禁用: " + request.getTemplateCode());
        }
        Long resolvedScenicId = resolveScenicId(template, request.getScenicId());

        // 2.5 素材版本缓存检查（减少数据库查询）
        // 如果缓存存在，说明自上次成功生成后素材没有变化，可以直接复用历史记录
        if (request.getFaceId() != null && !faceStatusManager.isPuzzleSourceChanged(request.getFaceId(), template.getId(), 0)) {
            PuzzleGenerationRecordEntity cachedRecord = recordMapper.findLatestSuccessByFaceAndTemplate(
                    request.getFaceId(), template.getId());
            if (cachedRecord != null) {
                long duration = System.currentTimeMillis() - startTime;
                log.info("素材版本缓存命中，复用历史记录: faceId={}, templateId={}, recordId={}, imageUrl={}, duration={}ms",
                        request.getFaceId(), template.getId(), cachedRecord.getId(),
                        cachedRecord.getResultImageUrl(), duration);
                return cachedRecord.getId();
            }
            // 缓存存在但记录被删除，继续执行正常流程
            log.debug("素材版本缓存存在但历史记录不存在，继续正常生成: faceId={}, templateId={}",
                    request.getFaceId(), template.getId());
        }

        // 3. 查询并排序元素
        List<PuzzleElementEntity> elements = puzzleRepository.getElementsByTemplateId(template.getId());
        if (elements.isEmpty()) {
            throw new IllegalArgumentException("模板没有配置元素: " + request.getTemplateCode());
        }
        elements.sort(Comparator.comparing(PuzzleElementEntity::getZIndex,
                Comparator.nullsFirst(Comparator.naturalOrder())));

        // 4. 构建dynamicData
        Map<String, String> finalDynamicData = buildDynamicData(template, request, resolvedScenicId, elements);

        // 5. 重复图片检测（可能抛出DuplicateImageException）
        duplicationDetector.detectDuplicateImages(finalDynamicData, elements);

        // 6. 内容去重检测
        String contentHash = duplicationDetector.calculateContentHash(finalDynamicData);
        PuzzleGenerationRecordEntity duplicateRecord = duplicationDetector.findDuplicateRecord(
                template.getId(), contentHash, resolvedScenicId
        );
        if (duplicateRecord != null) {
            long duration = System.currentTimeMillis() - startTime;
            log.info("检测到重复内容，复用历史记录: recordId={}, status={}, imageUrl={}, duration={}ms",
                    duplicateRecord.getId(), duplicateRecord.getStatus(), duplicateRecord.getResultImageUrl(), duration);
            // 仅成功记录才标记素材版本缓存（生成中的记录可能会失败）
            if (request.getFaceId() != null && duplicateRecord.getStatus() == 1) {
                faceStatusManager.markPuzzleSourceVersion(request.getFaceId(), template.getId(), 0);
            }
            return duplicateRecord.getId();
        }

        // 7. 创建生成记录
        PuzzleGenerationRecordEntity record = createRecord(template, request, resolvedScenicId);
        record.setContentHash(contentHash);
        recordMapper.insert(record);

        // 清除生成记录缓存（新记录插入后列表和数量都会变化）
        puzzleRepository.clearRecordCacheByFace(request.getFaceId());

        // 8. 创建边缘渲染任务
        Long taskId = puzzleEdgeRenderTaskService.createRenderTask(
                record,
                template,
                elements,
                finalDynamicData,
                request.getOutputFormat(),
                request.getQuality()
        );

        // 异步任务：在回调成功后标记缓存（由边缘渲染服务在成功回调中处理）
        // 这里只记录请求信息供后续使用
        long duration = System.currentTimeMillis() - startTime;
        log.info("异步拼图任务已进入边缘渲染队列: recordId={}, taskId={}, templateCode={}, duration={}ms",
                record.getId(), taskId, request.getTemplateCode(), duration);

        return record.getId();
    }

    /**
     * 等待生成中的记录完成
     * 轮询数据库直到记录状态变为非生成中（成功或失败），或超时返回null
     *
     * @param recordId  记录ID
     * @param timeoutMs 超时时间（毫秒）
     * @return 完成后的记录，超时返回null
     */
    private PuzzleGenerationRecordEntity waitForRecordCompletion(Long recordId, long timeoutMs) {
        long deadline = System.currentTimeMillis() + timeoutMs;
        while (System.currentTimeMillis() < deadline) {
            PuzzleGenerationRecordEntity record = recordMapper.getById(recordId);
            if (record == null || record.getStatus() != 0) {
                return record;
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.warn("等待记录完成被中断: recordId={}", recordId);
                return null;
            }
        }
        log.warn("等待记录完成超时: recordId={}, timeoutMs={}", recordId, timeoutMs);
        return null;
    }

    /**
     * 校验请求参数
     */
    private void validateRequest(PuzzleGenerateRequest request) {
        if (request.getFaceId() == null) {
            throw new IllegalArgumentException("人脸ID不能为空");
        }
    }

    /**
     * 创建生成记录
     */
    private PuzzleGenerationRecordEntity createRecord(PuzzleTemplateEntity template,
                                                     PuzzleGenerateRequest request,
                                                     Long scenicId) {
        PuzzleGenerationRecordEntity record = new PuzzleGenerationRecordEntity();
        record.setTemplateId(template.getId());
        record.setTemplateCode(template.getCode());
        record.setUserId(request.getUserId());
        record.setFaceId(request.getFaceId());
        record.setBusinessType(request.getBusinessType());
        record.setScenicId(scenicId);
        record.setStatus(0); // 生成中
        record.setRetryCount(0);

        // 将动态数据保存为JSON
        if (request.getDynamicData() != null && !request.getDynamicData().isEmpty()) {
            record.setGenerationParams(JSONUtil.toJsonStr(request.getDynamicData()));
        }

        return record;
    }

    /**
     * 构建dynamicData(合并自动填充和手动数据)
     * 优先级: 手动传入的数据 > 自动填充的数据
     */
    private Map<String, String> buildDynamicData(PuzzleTemplateEntity template,
                                                 PuzzleGenerateRequest request,
                                                 Long scenicId,
                                                 List<PuzzleElementEntity> elements) {
        Map<String, String> dynamicData = new HashMap<>();

        // 0. 检查是否需要自动生成 travelResultWxaCode 二维码
        if (request.getFaceId() != null && scenicId != null) {
            boolean hasTravelResultWxaCode = elements.stream()
                    .anyMatch(e -> "travelResultWxaCode".equals(e.getElementKey()));

            if (hasTravelResultWxaCode && !dynamicDataContainsKey(request.getDynamicData(), "travelResultWxaCode")) {
                String qrcodeUrl = generateWechatQrcode(request.getFaceId(), scenicId);
                if (qrcodeUrl != null) {
                    dynamicData.put("travelResultWxaCode", qrcodeUrl);
                    log.info("自动生成微信小程序二维码成功: faceId={}, url={}", request.getFaceId(), qrcodeUrl);
                }
            }
        }

        // 1. 自动填充(基于faceId和规则)
        boolean ruleMatched = false;
        if (request.getFaceId() != null) {
            try {
                FillResult fillResult = fillEngine.execute(
                        template.getId(),
                        request.getFaceId(),
                        scenicId
                );

                ruleMatched = fillResult.isRuleMatched();

                if (fillResult.isRuleMatched()) {
                    log.info("自动填充成功: 匹配规则[{}], 填充了{}个元素",
                            fillResult.getMatchedRuleName(),
                            fillResult.getFilledCount());
                    dynamicData.putAll(fillResult.getDynamicData());
                } else {
                    log.info("自动填充未匹配任何规则, templateId={}, faceId={}",
                            template.getId(), request.getFaceId());
                }
            } catch (Exception e) {
                log.error("自动填充异常, templateId={}, faceId={}", template.getId(), request.getFaceId(), e);
                // 自动填充失败不影响整体流程,继续执行
            }
        }

        // 2. 检查是否必须匹配规则
        Boolean requireRuleMatch = request.getRequireRuleMatch();
        if (Boolean.TRUE.equals(requireRuleMatch) && !ruleMatched) {
            throw new IllegalArgumentException(
                    String.format("未匹配到任何填充规则,无法生成图片 (templateCode=%s, faceId=%s, requireRuleMatch=true)",
                            request.getTemplateCode(), request.getFaceId())
            );
        }

        // 3. 手动数据覆盖(优先级更高)
        if (request.getDynamicData() != null && !request.getDynamicData().isEmpty()) {
            dynamicData.putAll(request.getDynamicData());
            log.debug("合并手动传入的dynamicData, count={}", request.getDynamicData().size());
        }

        log.info("最终dynamicData: {}", dynamicData.keySet());
        return dynamicData;
    }

    /**
     * 检查dynamicData中是否包含指定key
     */
    private boolean dynamicDataContainsKey(Map<String, String> dynamicData, String key) {
        return dynamicData != null && dynamicData.containsKey(key);
    }

    /**
     * 生成微信小程序二维码
     *
     * @param faceId 人脸ID
     * @param scenicId 景区ID
     * @return 二维码URL,失败返回null
     */
    private String generateWechatQrcode(Long faceId, Long scenicId) {
        File qrcode = null;
        try {
            // 获取景区的小程序配置
            MpConfigEntity scenicMpConfig = scenicRepository.getScenicMpConfig(scenicId);
            if (scenicMpConfig == null) {
                log.error("生成微信小程序二维码失败: 未找到景区[{}]的小程序配置", scenicId);
                return null;
            }

            // 生成临时文件
            qrcode = new File("qrcode_" + faceId + "_" + UUID.randomUUID().toString().substring(0, 8) + ".jpg");

            // 调用微信API生成小程序码
            WxMpUtil.generateUnlimitedWXAQRCode(
                    scenicMpConfig.getAppId(),
                    scenicMpConfig.getAppSecret(),
                    "pages/videoSynthesis/from_face",
                    faceId.toString(),
                    qrcode
            );

            // 上传到OSS
            try (FileInputStream fis = new FileInputStream(qrcode)) {
                String fileName = String.format("qrcode_%d.jpg", faceId);
                boolean exists = StorageFactory.use().isExists("puzzle", "wechat_qrcode", fileName);
                if (exists) {
                    log.debug("微信小程序二维码已存在, 不重复上传: faceId={}, url={}", faceId, StorageFactory.use().getUrl("puzzle", "wechat_qrcode", fileName));
                    return StorageFactory.use().getUrl("puzzle", "wechat_qrcode", fileName);
                }
                return StorageFactory.use().uploadFile(
                        "image/jpeg",
                        fis,
                        "puzzle",
                        "wechat_qrcode",
                        fileName
                );
            }
        } catch (Exception e) {
            log.error("生成微信小程序二维码失败: faceId={}, scenicId={}", faceId, scenicId, e);
            return null;
        } finally {
            // 清理临时文件
            if (qrcode != null && qrcode.exists()) {
                try {
                    Files.delete(qrcode.toPath());
                } catch (Exception e) {
                    log.warn("删除临时二维码文件失败: {}", qrcode.getAbsolutePath(), e);
                }
            }
        }
    }

    /**
     * 校验模板与请求景区的合法性
     *
     * @param template        模板
     * @param requestedScenic 请求中的景区ID
     * @return 最终生效的景区ID
     */
    private Long resolveScenicId(PuzzleTemplateEntity template, Long requestedScenic) {
        Long templateScenicId = template.getScenicId();
        if (templateScenicId == null) {
            return requestedScenic;
        }

        if (requestedScenic == null) {
            throw new IllegalArgumentException("模板绑定景区, scenicId为必填项");
        }

        if (!templateScenicId.equals(requestedScenic)) {
            throw new IllegalArgumentException("模板不属于当前景区, 请检查templateCode与scenicId");
        }

        return templateScenicId;
    }
}
