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

import cn.hutool.core.date.DateUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.biz.TemplateBiz;
import com.ycwl.basic.constant.BaseContextHandler;
import com.ycwl.basic.exception.BaseException;
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
import com.ycwl.basic.face.pipeline.enums.FaceMatchingScene;
import com.ycwl.basic.face.pipeline.factory.FaceMatchingPipelineFactory;
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
import com.ycwl.basic.facebody.entity.SearchFaceResultItem;
import com.ycwl.basic.integration.device.dto.device.DeviceV2DTO;
import com.ycwl.basic.mapper.FaceSampleAssociationMapper;
import com.ycwl.basic.mapper.FaceSampleMapper;
import com.ycwl.basic.mapper.ProjectMapper;
import com.ycwl.basic.mapper.SourceMapper;
import com.ycwl.basic.mapper.StatisticsMapper;
import com.ycwl.basic.mapper.FaceMapper;
import com.ycwl.basic.mapper.VideoMapper;
import com.ycwl.basic.mapper.OrderMapper;
import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
import com.ycwl.basic.model.mobile.face.FaceStatusResp;
import com.ycwl.basic.model.mobile.goods.VideoTaskStatusVO;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO;
import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.mobile.face.FaceRecognitionUpdateReq;
import com.ycwl.basic.model.pc.face.req.FaceReqQuery;
import com.ycwl.basic.model.mobile.face.FaceRecognitionDetailVO;
import com.ycwl.basic.model.mobile.face.FaceRecognitionSampleVO;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.model.pc.project.resp.ProjectRespVO;
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.pc.source.req.SourceReqQuery;
import com.ycwl.basic.model.pc.source.resp.SourceRespVO;
import com.ycwl.basic.model.pc.order.entity.OrderEntity;
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
import com.ycwl.basic.model.pc.video.entity.MemberVideoEntity;
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
import com.ycwl.basic.model.repository.TaskUpdateResult;

import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
import com.ycwl.basic.constant.FreeStatus;
import com.ycwl.basic.model.pc.puzzle.entity.MemberPuzzleEntity;
import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity;
import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity;
import com.ycwl.basic.puzzle.mapper.MemberPuzzleMapper;
import com.ycwl.basic.puzzle.mapper.PuzzleGenerationRecordMapper;
import com.ycwl.basic.puzzle.mapper.PuzzleTemplateMapper;
import com.ycwl.basic.puzzle.repository.PuzzleRepository;
import com.ycwl.basic.repository.DeviceRepository;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.MemberRelationRepository;
import com.ycwl.basic.repository.OrderRepository;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.repository.SourceRepository;
import com.ycwl.basic.repository.TemplateRepository;
import com.ycwl.basic.repository.VideoRepository;
import com.ycwl.basic.repository.VideoTaskRepository;
import com.ycwl.basic.service.mobile.GoodsService;
import com.ycwl.basic.service.pc.FaceDetectLogAiCamService;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.constant.SourceType;
import com.ycwl.basic.service.pc.helper.FaceMatchDedupService;
import com.ycwl.basic.service.pc.helper.FaceMetricsRecorder;
import com.ycwl.basic.service.pc.helper.SearchResultMerger;
import com.ycwl.basic.service.pc.helper.ScenicConfigFacade;
import com.ycwl.basic.service.pc.orchestrator.FaceMatchingOrchestrator;
import com.ycwl.basic.service.pc.processor.BuyStatusProcessor;
import com.ycwl.basic.service.pc.processor.SourceRelationProcessor;
import com.ycwl.basic.service.pc.processor.VideoRecreationHandler;
import com.ycwl.basic.service.pc.strategy.RematchContext;
import com.ycwl.basic.service.pc.strategy.RematchModeStrategy;
import com.ycwl.basic.service.pc.strategy.RematchStrategyFactory;
import com.ycwl.basic.service.printer.PrinterService;
import com.ycwl.basic.service.task.TaskFaceService;
import com.ycwl.basic.service.task.TaskService;
import com.ycwl.basic.storage.StorageFactory;
import com.ycwl.basic.storage.adapters.IStorageAdapter;
import com.ycwl.basic.storage.enums.StorageAcl;
import com.ycwl.basic.storage.utils.StorageUtil;
import com.ycwl.basic.utils.*;
import com.ycwl.basic.pipeline.core.Pipeline;
import com.ycwl.basic.pipeline.exception.PipelineException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ycwl.basic.constant.FaceConstant.USER_FACE_DB_NAME;
import static com.ycwl.basic.constant.StorageConstant.USER_FACE;

/**
 * @Author：longbinbin
 * @Date：2024/12/2 16:39
 */
@Slf4j
@Service
public class FaceServiceImpl implements FaceService {
    private static final String FACE_UPLOAD_SCENE_PRINTER_TV = "tv";

    @Autowired
    private FaceMapper faceMapper;
    @Autowired
    private TaskFaceService faceService;
    @Autowired
    private StatisticsMapper statisticsMapper;

    @Autowired
    private TaskService taskTaskService;
    @Autowired
    private SourceMapper sourceMapper;
    @Autowired
    private OrderBiz orderBiz;
    @Autowired
    private FaceRepository faceRepository;
    @Autowired
    private VideoMapper videoMapper;
    @Autowired
    private ScenicRepository scenicRepository;
    @Autowired
    private ScenicService scenicService;
    @Autowired
    private VideoRepository videoRepository;
    @Autowired
    private VideoTaskRepository videoTaskRepository;
    @Autowired
    private TemplateBiz templateBiz;
    @Autowired
    private DeviceRepository deviceRepository;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private FaceSampleMapper faceSampleMapper;
    @Autowired
    private FaceSampleAssociationMapper faceSampleAssociationMapper;
    @Autowired
    private GoodsService goodsService;
    @Autowired
    private ProjectMapper projectMapper;
    @Autowired
    private MemberRelationRepository memberRelationRepository;
    @Autowired
    private TemplateRepository templateRepository;
    @Autowired
    private PrinterService printerService;

    // 第一阶段的辅助类
    @Autowired
    private FaceMetricsRecorder metricsRecorder;
    @Autowired
    private SearchResultMerger resultMerger;
    @Autowired
    private RematchStrategyFactory rematchStrategyFactory;
    @Autowired
    private ScenicConfigFacade scenicConfigFacade;

    // 编排器
    @Autowired
    private FaceMatchingOrchestrator faceMatchingOrchestrator;
    @Autowired
    private FaceMatchingPipelineFactory faceMatchingPipelineFactory;

    // 防重复服务
    @Autowired
    private FaceMatchDedupService faceMatchDedupService;

    // 第二阶段的处理器
    @Autowired
    private SourceRelationProcessor sourceRelationProcessor;
    @Autowired
    private BuyStatusProcessor buyStatusProcessor;
    @Autowired
    private VideoRecreationHandler videoRecreationHandler;
    @Autowired
    private PuzzleGenerationRecordMapper puzzleGenerationRecordMapper;
    @Autowired
    private PuzzleTemplateMapper puzzleTemplateMapper;
    @Autowired
    private PuzzleRepository puzzleRepository;
    @Autowired
    private MemberPuzzleMapper memberPuzzleMapper;
    @Autowired
    private FaceDetectLogAiCamService faceDetectLogAiCamService;
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private com.ycwl.basic.biz.FaceStatusManager faceStatusManager;
    @Autowired
    private SourceRepository sourceRepository;

    @Override
    public ApiResponse<PageInfo<FaceRespVO>> pageQuery(FaceReqQuery faceReqQuery) {
        PageHelper.startPage(faceReqQuery.getPageNum(),faceReqQuery.getPageSize());
        List<FaceRespVO> list = faceMapper.list(faceReqQuery);
        PageInfo<FaceRespVO> pageInfo = new PageInfo<>(list);
        return ApiResponse.success(pageInfo);
    }

    @Override
    public ApiResponse<List<FaceRespVO>> list(FaceReqQuery faceReqQuery) {
        return ApiResponse.success(faceMapper.list(faceReqQuery));
    }

    @Override
    public ApiResponse<FaceRespVO> getById(Long id) {
        return ApiResponse.success(faceMapper.getById(id));
    }

    @Override
    public ApiResponse<Integer> add(FaceEntity face) {
        face.setId(SnowFlakeUtil.getLongId());
        int i = faceMapper.add(face);
        if (i == 0) {
            return ApiResponse.fail("添加失败");
        }
        return ApiResponse.success(i);
    }

    @Override
    public ApiResponse<Integer> deleteById(Long id) {
        int i = faceMapper.deleteById(id);
        if (i == 0) {
            return ApiResponse.fail("删除失败");
        }
        return ApiResponse.success(i);
    }

    @Override
    public ApiResponse<Integer> deleteByIds(List<Long> ids) {
        int i = faceMapper.deleteByIds(ids);
        if (i == 0) {
            return ApiResponse.fail("删除失败");
        }
        return ApiResponse.success(i);
    }

    @Override
    public FaceRecognizeResp faceUpload(MultipartFile file, Long scenicId, Long userId, String scene) {
        //1、上传人脸照片
        IStorageAdapter adapter = StorageFactory.use("faces");
        String filePath = StorageUtil.joinPath(USER_FACE, DateUtil.format(new Date(),"yyyy-MM-dd"));
        String originalFilename = file.getOriginalFilename();
        String suffix = originalFilename.split("\\.", 2)[1];
        String fileName = UUID.randomUUID() + "." + suffix;
        String faceUrl = adapter.uploadFile(file, filePath, fileName);
        Long newFaceId = SnowFlakeUtil.getLongId();
        Long oldFaceId = null;
        IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(scenicId);
        faceService.assureFaceDb(faceBodyAdapter, USER_FACE_DB_NAME+scenicId);
        SearchFaceRespVo userDbSearchResult = faceService.searchFace(faceBodyAdapter, USER_FACE_DB_NAME+scenicId, faceUrl, "判断是否为用户上传过的人脸");
        float strictScore = 0.75F;
        if (userDbSearchResult == null) {
            // 都是null了,那得是新的
            faceBodyAdapter.addFace(USER_FACE_DB_NAME+scenicId, newFaceId.toString(), faceUrl, newFaceId.toString());
        } else if (userDbSearchResult.getSampleListIds() == null || userDbSearchResult.getSampleListIds().isEmpty()) {
            // 没有匹配到过,也得是新的
            faceBodyAdapter.addFace(USER_FACE_DB_NAME+scenicId, newFaceId.toString(), faceUrl, newFaceId.toString());
        } else if (userDbSearchResult.getFirstMatchRate() < strictScore) {
            // 有匹配结果,但是不匹配旧的
            faceBodyAdapter.addFace(USER_FACE_DB_NAME+scenicId, newFaceId.toString(), faceUrl, newFaceId.toString());
        } else {
            // 有匹配结果,且能匹配旧的数据
            Optional<Long> faceAny = userDbSearchResult.getSampleListIds().stream().filter(_faceId -> {
                FaceEntity face = faceRepository.getFace(_faceId);
                if (face == null) {
                    return false;
                }
                return face.getMemberId().equals(userId);
            }).findAny();
            if (faceAny.isPresent()) {
                oldFaceId = faceAny.get();
                FaceRespVO oldFace = faceMapper.getById(oldFaceId);
                if (oldFace == null) {
                    faceBodyAdapter.deleteFace(USER_FACE_DB_NAME+scenicId, oldFaceId.toString());
                    faceBodyAdapter.addFace(USER_FACE_DB_NAME+scenicId, newFaceId.toString(), faceUrl, newFaceId.toString());
                    oldFaceId = null;
                } else {
                    newFaceId = oldFaceId;
                }
            }
        }
        if (oldFaceId==null) {
            //新增人脸
            FaceEntity faceEntity = new FaceEntity();
            faceEntity.setCreateAt(new Date());
            faceEntity.setScenicId(scenicId);
            faceEntity.setMemberId(userId);
            faceEntity.setFaceUrl(faceUrl);
            faceEntity.setId(newFaceId);
            faceMapper.add(faceEntity);
        } else {
            //更新人脸
            FaceEntity faceEntity = new FaceEntity();
            faceEntity.setId(oldFaceId);
            faceEntity.setFaceUrl(faceUrl);
            faceMapper.update(faceEntity);
            faceRepository.clearFaceCache(oldFaceId);
        }
        FaceRecognizeResp resp = new FaceRecognizeResp();
        resp.setUrl(faceUrl);
        resp.setFaceId(newFaceId);
        resp.setScenicId(scenicId);
        matchFaceId(newFaceId, oldFaceId == null, scene);

        // 异步执行自动添加打印
        Long finalFaceId = newFaceId;
        Thread thread = new Thread(() -> printerService.autoAddPhotosToPreferPrint(finalFaceId), "auto-add-print-" + newFaceId);
        thread.start();

        new Thread(() -> {
            try {
                bindWxaCode(finalFaceId);
                log.debug("预生成小程序码成功, faceId: {}", finalFaceId);
            } catch (Exception e) {
                log.warn("预生成小程序码失败, faceId: {}, 原因: {}", finalFaceId, e.getMessage());
            }
        }, "pre-generate-wxacode-" + newFaceId).start();
        if (Strings.CI.equals("print", scene)) {
            try {
                thread.join();
            } catch (InterruptedException ignore) {

            }
        }
        if (Strings.CI.equals("aiCam", scene)) {
            faceDetectLogAiCamService.searchAndLog(scenicId, newFaceId, faceUrl, faceBodyAdapter);
        }

        return resp;
    }

    @Override
    public List<FaceRespVO> listByUser(Long userId, Long scenicId) {
        return faceMapper.listByScenicAndUserId(scenicId, userId);
    }


    @Override
    public SearchFaceRespVo matchFaceId(Long faceId) {
        // 获取人脸信息用于防重检查
        FaceEntity face = faceRepository.getFace(faceId);
        if (face == null) {
            log.warn("人脸不存在，无法执行匹配，faceId: {}", faceId);
            return null;
        }

        // 防重复检查：如果2秒内已调用过，则跳过
        if (faceMatchDedupService.shouldSkip(face.getMemberId(), face.getScenicId())) {
            log.info("防重复：跳过人脸匹配。faceId={}, userId={}, scenicId={}",
                     faceId, face.getMemberId(), face.getScenicId());
            return null; // 静默忽略，返回null
        }

        // 正常执行人脸匹配
        SearchFaceRespVo result = faceMatchingOrchestrator.orchestrateMatching(faceId, false, null);

        // 执行完成后标记，防止2秒内重复调用
        faceMatchDedupService.markMatched(face.getMemberId(), face.getScenicId());

        return result;
    }

    @Override
    public SearchFaceRespVo matchFaceId(Long faceId, boolean isNew) {
        return matchFaceId(faceId, isNew, null);
    }
    @Override
    public SearchFaceRespVo matchFaceId(Long faceId, boolean isNew, String scene) {
        if (Strings.CI.equals(FACE_UPLOAD_SCENE_PRINTER_TV, scene)) {
            return matchFaceIdForPrinterTv(faceId, isNew);
        }
        return faceMatchingOrchestrator.orchestrateMatching(faceId, isNew, scene);
    }

    private SearchFaceRespVo matchFaceIdForPrinterTv(Long faceId, boolean isNew) {
        FaceMatchingContext context = FaceMatchingContext.builder()
                .faceId(faceId)
                .isNew(isNew)
                .scene(FaceMatchingScene.PRINTER_TV_RECOGNIZE)
                .build();

        Pipeline<FaceMatchingContext> pipeline = faceMatchingPipelineFactory.createPrinterTvRecognizePipeline();
        try {
            boolean success = pipeline.execute(context);
            if (!success) {
                throw new BaseException("打印机大屏人脸识别失败");
            }
            return context.getSearchResult();
        } catch (PipelineException e) {
            Throwable rootCause = e.getCause();
            if (rootCause instanceof BaseException baseException) {
                throw baseException;
            }
            throw new BaseException("打印机大屏人脸识别失败");
        }
    }

    /**
     * 关联样本扩展
     * 根据 face_sample_association 表，将同组样本ID加入匹配结果
     */
    private void expandSampleAssociation(SearchFaceRespVo searchResult, Long scenicId, Long faceId) {
        List<Long> sampleListIds = searchResult.getSampleListIds();
        if (sampleListIds == null || sampleListIds.isEmpty()) {
            return;
        }
        try {
            List<Long> associatedIds = faceSampleAssociationMapper.findAssociatedSampleIds(scenicId, sampleListIds);
            if (associatedIds == null || associatedIds.isEmpty()) {
                return;
            }
            Set<Long> currentSet = new HashSet<>(sampleListIds);
            List<Long> netNewIds = associatedIds.stream()
                    .filter(id -> !currentSet.contains(id))
                    .distinct()
                    .collect(Collectors.toList());
            if (!netNewIds.isEmpty()) {
                List<Long> expandedIds = new ArrayList<>(sampleListIds);
                expandedIds.addAll(netNewIds);
                searchResult.setSampleListIds(expandedIds);
                log.info("关联样本扩展完成: faceId={}, scenicId={}, 原始数={}, 新增数={}, 扩展后={}",
                        faceId, scenicId, sampleListIds.size(), netNewIds.size(), expandedIds.size());
            }
        } catch (Exception e) {
            log.error("关联样本扩展失败, faceId={}, scenicId={}", faceId, scenicId, e);
        }
    }

    /**
     * 更新人脸实体结果信息
     * 仅用于 handleCustomFaceMatching 方法
     */
    private void updateFaceEntityResult(FaceEntity originalFace, SearchFaceRespVo searchResult, Long faceId) {
        FaceEntity faceEntity = new FaceEntity();
        faceEntity.setId(faceId);
        faceEntity.setScore(searchResult.getScore());
        faceEntity.setMatchResult(searchResult.getSearchResultJson());

        if (searchResult.getFirstMatchRate() != null) {
            faceEntity.setFirstMatchRate(BigDecimal.valueOf(searchResult.getFirstMatchRate()));
        }

        if (searchResult.getSampleListIds() != null) {
            faceEntity.setMatchSampleIds(searchResult.getSampleListIds().stream()
                    .map(String::valueOf)
                    .collect(Collectors.joining(",")));
        }

        faceEntity.setCreateAt(new Date());
        faceEntity.setScenicId(originalFace.getScenicId());
        faceEntity.setMemberId(originalFace.getMemberId());
        faceEntity.setFaceUrl(originalFace.getFaceUrl());

        faceMapper.update(faceEntity);
        faceRepository.clearFaceCache(faceEntity.getId());

        log.debug("人脸结果更新完成：faceId={}, score={}, 匹配数={}",
                faceId, searchResult.getScore(),
                searchResult.getSampleListIds() != null ? searchResult.getSampleListIds().size() : 0);
    }

    @Override
    public ApiResponse<String> deleteFace(Long faceId) {
        FaceEntity face = faceRepository.getFace(faceId);
        faceMapper.deleteById(faceId);
        faceRepository.clearFaceCache(faceId);
        Thread.ofVirtual().start(() -> {
            sourceMapper.deleteNotBuyFaceRelation(face.getMemberId(), faceId);
            videoMapper.deleteNotBuyFaceRelations(face.getMemberId(), faceId);
            IFaceBodyAdapter adapter = scenicService.getScenicFaceBodyAdapter(face.getScenicId());
            adapter.deleteFace(USER_FACE_DB_NAME+face.getScenicId().toString(), faceId.toString());
        });
        return ApiResponse.success("删除成功");
    }


    @Override
    public List<ContentPageVO> faceContentList(Long faceId) {
        FaceEntity face = faceRepository.getFace(faceId);
        if (face == null) {
            return Collections.emptyList();
        }
        Long userId = Long.parseLong(BaseContextHandler.getUserId());
        ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
        if (Integer.valueOf(2).equals(scenicConfig.getInteger("scenic_mode", 0))) {
            List<ContentPageVO> result = new ArrayList<>();
            // 摄影师拍照
            List<DeviceV2DTO> deviceList = deviceRepository.getAllDeviceByScenicId(face.getScenicId());
            List<SourceEntity> sourceEntityList = sourceMapper.listSourceByFaceRelation(face.getId(), 2);
            List<MemberSourceEntity> memberSourceRelations = memberRelationRepository.listSourceByFaceRelation(face.getId(), 2);
            // 按 deviceList 顺序排列结果
            Set<Long> deviceIds = deviceList.stream().map(DeviceV2DTO::getId).collect(Collectors.toSet());
            for (DeviceV2DTO device : deviceList) {
                List<SourceEntity> deviceSources = sourceEntityList.stream()
                        .filter(s -> device.getId().equals(s.getDeviceId()))
                        .toList();
                if (deviceSources.isEmpty()) {
                    ContentPageVO content = new ContentPageVO();
                    content.setName(device.getName());
                    content.setGroup(device.getName());
                    content.setContentId(device.getId());
                    content.setGoodsType(2);
                    content.setContentType(2);
                    content.setScenicId(face.getScenicId());
                    content.setSourceType(2);
                    content.setTemplateCoverUrl("");
                    content.setIsBuy(0);
                    content.setLockType(1);
                    result.add(content);
                } else {
                    for (SourceEntity sourceEntity : deviceSources) {
                        ContentPageVO content = new ContentPageVO();
                        content.setName("摄影师拍照");
                        content.setGroup(device.getName());
                        content.setContentId(sourceEntity.getId());
                        content.setGoodsType(2);
                        content.setContentType(2);
                        content.setScenicId(sourceEntity.getScenicId());
                        content.setSourceType(2);
                        content.setOrigUrl(sourceEntity.getUrl());
                        content.setTemplateCoverUrl(sourceEntity.getThumbUrl());
                        memberSourceRelations.stream().filter(relation -> relation.getSourceId().equals(sourceEntity.getId())).findAny().ifPresent(relation -> {
                            content.setIsBuy(relation.getIsBuy());
                        });
                        content.setLockType(-1);
                        result.add(content);
                    }
                }
            }
            // 补充不属于任何设备的源
            for (SourceEntity sourceEntity : sourceEntityList) {
                if (sourceEntity.getDeviceId() != null && deviceIds.contains(sourceEntity.getDeviceId())) {
                    continue;
                }
                ContentPageVO content = new ContentPageVO();
                content.setName("摄影师拍照");
                content.setContentId(sourceEntity.getId());
                content.setGoodsType(2);
                content.setContentType(2);
                content.setScenicId(sourceEntity.getScenicId());
                content.setSourceType(2);
                content.setOrigUrl(sourceEntity.getUrl());
                content.setTemplateCoverUrl(sourceEntity.getThumbUrl());
                memberSourceRelations.stream().filter(relation -> relation.getSourceId().equals(sourceEntity.getId())).findAny().ifPresent(relation -> {
                    content.setIsBuy(relation.getIsBuy());
                });
                content.setLockType(-1);
                result.add(content);
            }
            return result;
        }
        List<TemplateRespVO> templateList = templateRepository.getTemplateListByScenicId(face.getScenicId());
        List<ContentPageVO> contentList = templateList.stream().map(template -> {
            ///  select t.id templateId, t.scenic_id, t.`group`, t.`name`, pid, t.cover_url templateCoverUrl,
            ///                0 as sourceType, sort,
            ///                t.create_time, t.price
            ///         from template t
            ///         where t.scenic_id = #{scenicId} and pid = 0 and t.status = 1
            ///         order by sort
            ContentPageVO content = new ContentPageVO();
            content.setTemplateId(template.getId());
            content.setScenicId(template.getScenicId());
            content.setGroup(template.getGroup());
            content.setName(template.getName());
            content.setTemplateCoverUrl(template.getCoverUrl());
            content.setSourceType(0);
            content.setSort(template.getSort());
            content.setGoodsType(0);
            content.setScenicName(template.getScenicName());
            return content;
        }).peek(contentPageVO -> {
            List<MemberVideoEntity> memberVideoEntityList = memberRelationRepository.listRelationByFaceAndTemplate(faceId, contentPageVO.getTemplateId());
            contentPageVO.setGoodsType(0);
            contentPageVO.setContentType(1);
            contentPageVO.setSort(contentPageVO.getSort());
            if (!memberVideoEntityList.isEmpty()) {
                contentPageVO.setContentId(memberVideoEntityList.getFirst().getVideoId());
                boolean isBuy = orderRepository.checkUserBuyFaceItem(userId, faceId, 0, contentPageVO.getContentId());
                if (!isBuy) {
                    isBuy = orderRepository.checkUserBuyFaceItem(userId, faceId, -1, contentPageVO.getTemplateId()); // 购买模板
                }
                contentPageVO.setIsBuy(isBuy ? 1 : 0);
                VideoEntity video = videoRepository.getVideo(contentPageVO.getContentId());
                if (video != null) {
                    contentPageVO.setVideoUrl(video.getVideoUrl());
                    contentPageVO.setDuration(video.getDuration());
                    contentPageVO.setLockType(-1);
                    TaskUpdateResult updResult = videoTaskRepository.checkTaskUpdate(video.getTaskId());
                    contentPageVO.setNewSegmentCount(updResult.getNewSegmentCount());
                } else {
                    TaskEntity taskById = videoTaskRepository.getTaskById(memberVideoEntityList.getFirst().getTaskId());
                    if (taskById == null) {
                        contentPageVO.setLockType(0);
                    } else if (taskById.getStatus() == 3) {
                        contentPageVO.setLockType(2);
                    } else if (taskById.getStatus() == 0 || taskById.getStatus() == 2) {
                        contentPageVO.setLockType(-9); // 正在生成
                    }
                    contentPageVO.setContentType(0);
                }
            } else {
                contentPageVO.setContentType(0);
                boolean canGenerate = templateBiz.determineTemplateCanGenerate(contentPageVO.getTemplateId(), faceId);
                if (canGenerate) {
                    contentPageVO.setLockType(0);
                } else {
                    contentPageVO.setLockType(1);
                }
            }
        }).collect(Collectors.toList());

        // 拼图模板（使用缓存）
        List<PuzzleTemplateEntity> puzzleTemplateEntityList = puzzleRepository.listTemplateByScenic(face.getScenicId());
        if (!puzzleTemplateEntityList.isEmpty()) {
            List<PuzzleGenerationRecordEntity> records = puzzleGenerationRecordMapper.listByFaceId(faceId);
            PuzzleTemplateEntity template = puzzleTemplateEntityList.getFirst();
            Optional<PuzzleGenerationRecordEntity> optionalRecord = records.stream().filter(r -> r.getTemplateId().equals(template.getId())).findFirst();
            ContentPageVO sfpContent = new ContentPageVO();
            sfpContent.setName(template.getName());
            sfpContent.setGroup("氛围拼图");
            sfpContent.setScenicId(face.getScenicId());
            sfpContent.setContentType(3);
            sfpContent.setSourceType(3);
            sfpContent.setLockType(-1);
            sfpContent.setContentId(optionalRecord.map(PuzzleGenerationRecordEntity::getId).orElse(null));
            sfpContent.setTemplateId(template.getId());
            sfpContent.setTemplateCoverUrl(template.getCoverImage());
            sfpContent.setGoodsType(3);
            sfpContent.setSort(0);
            if (optionalRecord.isPresent()) {
                IsBuyRespVO isBuyScenic = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), faceId, 5, face.getScenicId());
                if (isBuyScenic.isBuy()) {
                    sfpContent.setIsBuy(1);
                } else {
                    IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), faceId, 5, optionalRecord.get().getTemplateId());
                    if (isBuyRespVO.isBuy()) {
                        sfpContent.setIsBuy(1);
                    } else {
                        sfpContent.setIsBuy(0);
                    }
                }
            }
            // 从 member_puzzle 关联记录读取免费状态
            if (optionalRecord.isPresent()) {
                MemberPuzzleEntity memberPuzzle = memberPuzzleMapper.getByFaceAndRecord(faceId, optionalRecord.get().getId());
                if (memberPuzzle != null && FreeStatus.isFree(memberPuzzle.getIsFree())) {
                    sfpContent.setFreeCount(1);
                } else {
                    sfpContent.setFreeCount(0);
                }
            } else {
                sfpContent.setFreeCount(0);
            }
            contentList.add(1, sfpContent);
        }
        SourceReqQuery sourceReqQuery = new SourceReqQuery();
        sourceReqQuery.setScenicId(face.getScenicId());
        sourceReqQuery.setFaceId(faceId);
        sourceReqQuery.setMemberId(userId);
        //查询源素材
        List<SourceRespVO> sourceList = sourceMapper.queryByRelation(sourceReqQuery);
        ContentPageVO sourceVideoContent = new ContentPageVO();
        ContentPageVO sourceImageContent = new ContentPageVO();
        ContentPageVO sourceAiCamContent = new ContentPageVO();
        sourceVideoContent.setName("录像集");
        sourceImageContent.setName("照片集");
        sourceAiCamContent.setName("AI微单");
        sourceVideoContent.setSort(9999);
        sourceImageContent.setSort(9999);
        sourceAiCamContent.setSort(9999);
        sourceVideoContent.setScenicId(face.getScenicId());
        sourceImageContent.setScenicId(face.getScenicId());
        sourceAiCamContent.setScenicId(face.getScenicId());
        sourceVideoContent.setGoodsType(1);
        sourceImageContent.setGoodsType(2);
        sourceAiCamContent.setGoodsType(3);
        sourceVideoContent.setContentType(2);
        sourceImageContent.setContentType(2);
        sourceAiCamContent.setContentType(2);
        sourceVideoContent.setLockType(-1);
        sourceImageContent.setLockType(-1);
        sourceAiCamContent.setLockType(-1);
        sourceVideoContent.setGroup("直出原片");
        sourceImageContent.setGroup("直出原片");
        sourceAiCamContent.setGroup("智能连连拍");
        if (!scenicConfigFacade.isDisableSourceImage(face.getScenicId())) {
            IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), userId, faceId, SourceType.IMAGE.getCode(), faceId);
            sourceImageContent.setSourceType(isBuyRespVO.getGoodsType());
            sourceImageContent.setContentId(isBuyRespVO.getGoodsId());
            if (isBuyRespVO.isBuy()) {
                sourceImageContent.setIsBuy(1);
            } else {
                sourceImageContent.setIsBuy(0);
            }
            List<MemberSourceEntity> relations = memberRelationRepository.listSourceByFaceRelation(faceId, 2);
            if (!relations.isEmpty()) {
                sourceImageContent.setLockType(-1);
            } else {
                sourceImageContent.setLockType(1);
            }
            long freeCount = relations.stream().filter(entity -> Integer.valueOf(1).equals(entity.getIsFree())).count();
            sourceImageContent.setFreeCount((int) freeCount);
            contentList.add(sourceImageContent);
        }
        if (!scenicConfigFacade.isDisableSourceVideo(face.getScenicId())) {
            IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), userId, faceId, SourceType.VIDEO.getCode(), faceId);
            sourceVideoContent.setSourceType(isBuyRespVO.getGoodsType());
            sourceVideoContent.setContentId(isBuyRespVO.getGoodsId());
            if (isBuyRespVO.isBuy()) {
                sourceVideoContent.setIsBuy(1);
            } else {
                sourceVideoContent.setIsBuy(0);
            }
            List<MemberSourceEntity> relations = memberRelationRepository.listSourceByFaceRelation(faceId, 1);
            if (!relations.isEmpty()) {
                sourceVideoContent.setLockType(-1);
            } else {
                sourceVideoContent.setLockType(1);
            }
            long freeCount = relations.stream().filter(entity -> Integer.valueOf(1).equals(entity.getIsFree())).count();
            sourceVideoContent.setFreeCount((int) freeCount);
            contentList.add(sourceVideoContent);
        }
        // AI微单:只有存在type=3的数据时才添加
        boolean hasAiCam = sourceList.stream().anyMatch(source -> source.getType() == 3);
        if (hasAiCam) {
            IsBuyRespVO isBuyRespVO = orderBiz.isBuy(face.getScenicId(), userId, faceId, 13, faceId);
            sourceAiCamContent.setSourceType(isBuyRespVO.getGoodsType());
            sourceAiCamContent.setContentId(isBuyRespVO.getGoodsId());
            if (isBuyRespVO.isBuy()) {
                sourceAiCamContent.setIsBuy(1);
            } else {
                sourceAiCamContent.setIsBuy(0);
            }
            // AI微单有数据才显示,所以lockType固定为-1
            sourceAiCamContent.setLockType(-1);
            List<MemberSourceEntity> relations = memberRelationRepository.listSourceByFaceRelation(faceId, 3);
            long freeCount = relations.stream().filter(entity -> Integer.valueOf(1).equals(entity.getIsFree())).count();
            sourceAiCamContent.setFreeCount((int) freeCount);
            contentList.add(sourceAiCamContent);
        }
        sourceList.stream().collect(Collectors.groupingBy(SourceRespVO::getType)).forEach((type, list) -> {
            if (type == 1) {
                sourceVideoContent.setSourceType(1);
                sourceVideoContent.setLockType(-1);
                sourceVideoContent.setTemplateCoverUrl(list.getFirst().getUrl());
            } else if (type == 2) {
                sourceImageContent.setSourceType(2);
                sourceImageContent.setLockType(-1);
                sourceImageContent.setTemplateCoverUrl(list.getFirst().getUrl());
                if (StringUtils.isBlank(sourceVideoContent.getTemplateCoverUrl())) {
                    sourceVideoContent.setTemplateCoverUrl(list.getFirst().getUrl());
                }
            } else if (type == 3) {
                sourceAiCamContent.setSourceType(13);
                sourceAiCamContent.setLockType(-1);
                sourceAiCamContent.setTemplateCoverUrl(scenicConfig.getString("ai_camera_cover_url"));
            }
        });
        return contentList;
    }

    @Override
    public void bindFace(Long faceId, Long memberId) {
        FaceEntity face = faceRepository.getFace(faceId);
        if (face == null) {
            throw new BaseException("人脸数据不存在");
        }
        
        Long currentMemberId = face.getMemberId();
        if (currentMemberId.equals(memberId)) {
            return;
        }
        
        FaceEntity updateFace = new FaceEntity();
        updateFace.setId(faceId);
        updateFace.setMemberId(memberId);
        faceMapper.update(updateFace);
        
        sourceMapper.updateMemberIdByFaceId(faceId, memberId);
        
        videoMapper.updateMemberIdByFaceId(faceId, memberId);
        
        OrderEntity orderUpdate = new OrderEntity();
        orderUpdate.setFaceId(faceId);
        orderUpdate.setMemberId(memberId);
        orderMapper.updateMemberIdByFaceId(orderUpdate);
        
        faceRepository.clearFaceCache(faceId);
    }

    @Override
    public String bindWxaCode(Long faceId) {
        FaceEntity face = faceRepository.getFace(faceId);

        String dt = DateUtil.format(face.getCreateAt(), "yyyyMMdd");
        String path = "pages/videoSynthesis/bind_face";
        String filePath = "f" + faceId + ".jpg";
        IStorageAdapter adapter = StorageFactory.use();
        if (adapter.isExists(filePath)) {
            String url = adapter.getUrl(filePath);
            url = url.replace("-internal.aliyuncs.com", ".aliyuncs.com");
            return url;
        }
        MpConfigEntity mpConfig = scenicRepository.getScenicMpConfig(face.getScenicId());
        if (mpConfig == null) {
            throw new BaseException("小程序配置不存在");
        }
        String appId = mpConfig.getAppId();
        String appSecret = mpConfig.getAppSecret();
        try {
            File file = new File(filePath);
            WxMpUtil.generateUnlimitedWXAQRCode(appId, appSecret, path, faceId.toString(), file);
            String url = adapter.uploadFile(null, file, "wxa_face", dt, filePath);
            file.delete();
            adapter.setAcl(StorageAcl.PUBLIC_READ, filePath);
            url = url.replace("-internal.aliyuncs.com", ".aliyuncs.com");
            return url;
        } catch (Exception e) {
            throw new BaseException("生成二维码失败："+e.getMessage());
        }
    }

    @Override
    public FaceStatusResp getFaceStatus(Long faceId) {
        if (faceId == null) {
            return null;
        }

        FaceStatusResp statusResp = new FaceStatusResp();
        statusResp.setFaceId(faceId);

        FaceEntity face = faceRepository.getFace(faceId);
        if (face == null) {
            statusResp.setStep1Status(false);
            statusResp.setDisplayText("请重新录入人脸");
            return statusResp;
        }
        statusResp.setStep1Status(true);

        statusResp.setFaceUrl(face.getFaceUrl());

        // 查询识别次数
        long recognitionCount = metricsRecorder.getRecognitionCount(faceId);
        statusResp.setRecognitionCount(recognitionCount);

        // 查询是否触发过低阈值检测
        Boolean hasLowThreshold = metricsRecorder.hasLowThreshold(faceId);
        statusResp.setHasLowThreshold(hasLowThreshold);

        log.debug("查询人脸状态：faceId={}, recognitionCount={}, hasLowThreshold={}",
                 faceId, recognitionCount, hasLowThreshold);

        SourceReqQuery sourceReqQuery = new SourceReqQuery();
        sourceReqQuery.setMemberId(face.getMemberId());
        sourceReqQuery.setFaceId(faceId);
        sourceReqQuery.setType(2);
        ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
        List<MemberSourceEntity> countUser = memberRelationRepository.listSourceByFaceRelation(faceId, 2);
        if (countUser != null && !countUser.isEmpty()) {
            statusResp.setStep2Status(true);
        } else {
            statusResp.setStep2Status(false);
            if (Integer.valueOf(2).equals(scenicConfig.getInteger("scenic_mode", 0))) {
                statusResp.setDisplayText("Hey，去拍摄点免费拍照吧");
            } else {
                statusResp.setDisplayText("Hey，快去智能机位打卡吧");
            }
            return statusResp;
        }
        if (Integer.valueOf(2).equals(scenicConfig.getInteger("scenic_mode", 0))) {
            // 摄影模式
            if (!countUser.isEmpty()) {
                statusResp.setStep3Status(true);
                statusResp.setDisplayText("已为您拍摄" + countUser.size() + "张照片");
                return statusResp;
            } else {
                statusResp.setStep3Status(false);
                statusResp.setDisplayText("Hey，去拍摄点免费拍照吧");
                return statusResp;
            }
        } else {
            VideoTaskStatusVO taskStatusByFaceId = goodsService.getTaskStatusByFaceId(faceId);
            if (Integer.valueOf(1).equals(taskStatusByFaceId.getStatus())) {
                if (taskStatusByFaceId.getCount() > 0) {
                    statusResp.setStep3Status(true);
                    statusResp.setDisplayText("帧途AI已为您渲染"+ taskStatusByFaceId.getCount() +"个vlog");
                } else {
                    statusResp.setStep3Status(false);
                    statusResp.setDisplayText("帧途AI将会为您渲染vlog，请稍候");
                }
            } else {
                statusResp.setStep3Status(false);
                statusResp.setDisplayText("帧途AI正在为您渲染vlog，请稍候");
            }
        }
        return statusResp;
    }

    @Override
    public Boolean checkHasExtraCheck(Long faceId) {
        FaceEntity face = faceRepository.getFace(faceId);
        if (face == null) {
            return false;
        }
        ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());

        // 使用FaceMetricsRecorder获取计数信息
        long recognitionCount = metricsRecorder.getRecognitionCount(faceId);
        long customMatchCount = metricsRecorder.getCustomMatchCount(faceId);
        boolean hasLowThreshold = metricsRecorder.hasLowThreshold(faceId);

        Integer faceSelectMaxCount = scenicConfigFacade.getFaceSelectMaxCount(face.getScenicId());
        if (faceSelectMaxCount != null && faceSelectMaxCount > 0 && customMatchCount > faceSelectMaxCount) {
            log.debug("自定义人脸匹配次数超过限制：faceId={}, customMatchCount={}, limit={}",
                    faceId, customMatchCount, faceSelectMaxCount);
            return false;
        }

        Integer maxTourTime = scenicConfigFacade.getTourTime(face.getScenicId());
        Integer minTourTime = scenicConfigFacade.getTourMinTime(face.getScenicId());
        boolean tourMatch = false;
        if (maxTourTime != null && minTourTime != null) {
            if ((new Date().getTime()) - face.getCreateAt().getTime() < maxTourTime * 60 * 1000
                && (new Date().getTime()) - face.getCreateAt().getTime() > minTourTime * 60 * 1000) {
                tourMatch = true;
            }
        }
        // 判断是否项目匹配
        boolean projectMatch = false;
        if (maxTourTime != null) {
            List<Long> projectIdListForUser = statisticsMapper.getProjectIdListForUser(face.getMemberId(), new Date((new Date().getTime()) - maxTourTime * 60 * 1000), new Date());
            if (projectIdListForUser != null && !projectIdListForUser.isEmpty()) {
                Long projectId = projectIdListForUser.getFirst();
                ProjectRespVO projectRespVO = projectMapper.getById(projectId);
                if (projectRespVO != null) {
                    Integer maxPlayTime = projectRespVO.getMaxPlayTime();
                    Integer minPlayTime = projectRespVO.getMinPlayTime();
                    if (maxPlayTime != null && minPlayTime != null) {
                        if ((new Date().getTime()) - face.getCreateAt().getTime() < maxPlayTime * 60 * 1000
                                && (new Date().getTime()) - face.getCreateAt().getTime() > minPlayTime * 60 * 1000) {
                            projectMatch = true;
                        }
                    }
                }
            }
        }
        int ruleMatched = 0;
        if (recognitionCount > 1) {
            ruleMatched++;
        }
        if (tourMatch) {
            ruleMatched++;
        }
        if (projectMatch) {
            ruleMatched++;
        }

        // 使用策略模式替换switch语句
        Integer mode = scenicConfigFacade.getRematchMode(face.getScenicId());
        RematchContext context = RematchContext.builder()
                .recognitionCount(recognitionCount)
                .hasLowThreshold(hasLowThreshold)
                .tourMatch(tourMatch)
                .projectMatch(projectMatch)
                .ruleMatched(ruleMatched)
                .faceCreateAt(face.getCreateAt())
                .build();

        RematchModeStrategy strategy = rematchStrategyFactory.getStrategy(mode);
        return strategy.shouldRematch(context);
    }

    @Override
    public List<FaceSampleEntity> getLowMatchedFaceSamples(Long faceId) {
        FaceEntity face = faceMapper.get(faceId);
        if (face == null) {
            return List.of();
        }
        String matchResult = face.getMatchResult();
        if (matchResult == null || StringUtils.isBlank(matchResult)) {
            return List.of();
        }
        ScenicConfigManager configManager = scenicRepository.getScenicConfigManager(face.getScenicId());
        if (configManager == null) {
            return List.of();
        }
        Float lowThreshold = scenicConfigFacade.getFaceScoreLowThreshold(face.getScenicId());
        List<SearchFaceResultItem> resultItems = JacksonUtil.fromJsonToList(matchResult, SearchFaceResultItem.class);
        if (resultItems == null || resultItems.isEmpty()) {
            return List.of();
        }
        List<Long> idList = resultItems.stream().filter(item -> item.getScore() > lowThreshold/100F)
                .map(SearchFaceResultItem::getExtData).limit(9).map(Long::valueOf).toList();
        if (idList.isEmpty()) {
            return List.of();
        }
        List<FaceSampleEntity> sampleEntities = faceSampleMapper.listByIds(idList);
        return sampleEntities;
    }

    @Override
    public void matchCustomFaceId(Long faceId, List<Long> faceSampleIds) {
        handleCustomFaceMatching(faceId, faceSampleIds);
    }

    private SearchFaceRespVo handleCustomFaceMatching(Long faceId, List<Long> faceSampleIds) {
        if (faceId == null) {
            throw new IllegalArgumentException("faceId 不能为空");
        }
        if (faceSampleIds == null || faceSampleIds.isEmpty()) {
            throw new IllegalArgumentException("faceSampleIds 不能为空");
        }

        log.debug("开始自定义人脸匹配：faceId={}, faceSampleIds={}", faceId, faceSampleIds);

        // 记录自定义匹配调用次数，便于监控调用频率
        metricsRecorder.recordCustomMatchCount(faceId);

        try {
            FaceEntity face = faceRepository.getFace(faceId);
            if (face == null) {
                log.warn("人脸不存在，faceId: {}", faceId);
                throw new BaseException("人脸不存在");
            }

            List<FaceSampleEntity> faceSamples = faceSampleMapper.listByIds(faceSampleIds);
            if (faceSamples.isEmpty()) {
                log.warn("未找到指定的人脸样本，faceSampleIds: {}", faceSampleIds);
                throw new BaseException("未找到指定的人脸样本");
            }

            ScenicConfigManager configManager = scenicRepository.getScenicConfigManager(face.getScenicId());
            IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(face.getScenicId());

            if (faceBodyAdapter == null) {
                log.error("无法获取人脸识别适配器，scenicId: {}", face.getScenicId());
                throw new BaseException("人脸识别服务不可用，请稍后再试");
            }

            // 获取face_select_post_mode配置，默认为0（并集）
            Integer faceSelectPostMode = scenicConfigFacade.getFaceSelectPostMode(face.getScenicId());
            log.debug("face_select_post_mode配置值: {}", faceSelectPostMode);

            SearchFaceRespVo mergedResult;

            // 2. 根据face_select_post_mode决定搜索策略
            if (Integer.valueOf(2).equals(faceSelectPostMode)) {
                // 模式2：不搜索，直接使用用户选择的faceSampleIds
                log.debug("使用模式2：直接使用用户选择的人脸样本，不进行搜索");
                mergedResult = resultMerger.createDirectResult(faceSampleIds);
                mergedResult.setSearchResultJson(face.getMatchResult()); // 没有检索
            } else {
                // 模式0（并集）和模式1（交集）：需要进行搜索
                // 2.1 对每个faceSample进行人脸搜索
                List<SearchFaceRespVo> searchResults = new ArrayList<>();
                for (FaceSampleEntity faceSample : faceSamples) {
                    try {
                        SearchFaceRespVo result = faceService.searchFace(faceBodyAdapter,
                                                                       String.valueOf(face.getScenicId()),
                                                                       faceSample.getFaceUrl(),
                                                                       "自定义人脸匹配");
                        if (result != null) {
                            searchResults.add(result);
                        }
                    } catch (Exception e) {
                        log.warn("人脸样本搜索失败，faceSampleId={}, faceUrl={}",
                                faceSample.getId(), faceSample.getFaceUrl(), e);
                        // 继续处理其他样本，不中断整个流程
                    }
                }

                if (searchResults.isEmpty()) {
                    log.warn("所有人脸样本搜索都失败，faceId={}, faceSampleIds={}", faceId, faceSampleIds);
                    throw new BaseException("人脸识别失败，请重试");
                }

                // 2.2 根据模式整合多个搜索结果
                mergedResult = resultMerger.merge(searchResults, faceSelectPostMode);
            }

            // 3. 应用后置筛选逻辑
            if (mergedResult.getSampleListIds() != null && !mergedResult.getSampleListIds().isEmpty()) {
                List<FaceSampleEntity> allFaceSampleList = faceSampleMapper.listByIds(mergedResult.getSampleListIds());
                List<Long> filteredSampleIds = faceService.applySampleFilters(
                        mergedResult.getSampleListIds(), allFaceSampleList, configManager);
                mergedResult.setSampleListIds(filteredSampleIds);
                log.debug("应用后置筛选：原始样本数={}, 筛选后样本数={}",
                        allFaceSampleList.size(), filteredSampleIds.size());
            }

            // 4. 关联样本扩展
            expandSampleAssociation(mergedResult, face.getScenicId(), faceId);

            updateFaceEntityResult(face, mergedResult, faceId);

            List<Long> sampleListIds = mergedResult.getSampleListIds();
            if (sampleListIds != null && !sampleListIds.isEmpty()) {
                try {
                    // 在保存新关系前，删除该人脸的旧数据关系（member_source 和 member_video）
                    log.debug("删除人脸旧关系数据：faceId={}, memberId={}", faceId, face.getMemberId());
                    sourceMapper.deleteNotBuyFaceRelation(face.getMemberId(), faceId);
                    videoMapper.deleteNotBuyFaceRelations(face.getMemberId(), faceId);
                    memberRelationRepository.clearSCacheByFace(faceId);
                    faceStatusManager.invalidatePuzzleSourceVersion(faceId);
                    log.debug("人脸旧关系数据删除完成：faceId={}", faceId);

                    List<MemberSourceEntity> memberSourceEntityList = sourceRelationProcessor.processMemberSources(sampleListIds, face);

                    if (!memberSourceEntityList.isEmpty()) {
                        List<Long> freeSourceIds = sourceRelationProcessor.processFreeSourceLogic(memberSourceEntityList, face.getScenicId(), false);
                        buyStatusProcessor.processBuyStatus(memberSourceEntityList, freeSourceIds, face.getMemberId(),
                                face.getScenicId(), faceId);

                        videoRecreationHandler.handleVideoRecreation(face.getScenicId(), memberSourceEntityList, faceId,
                                face.getMemberId(), sampleListIds, false);

                        List<MemberSourceEntity> existingFiltered = sourceMapper.filterExistingRelations(memberSourceEntityList);
                        List<MemberSourceEntity> validFiltered = sourceMapper.filterValidSourceRelations(existingFiltered);
                        if (!validFiltered.isEmpty()) {
                            sourceMapper.addRelations(validFiltered);
                            log.debug("创建关联关系: faceId={}, 原始数量={}, 过滤后数量={}",
                                    faceId, memberSourceEntityList.size(), validFiltered.size());
                        } else {
                            log.warn("没有有效的关联关系可创建: faceId={}, 原始数量={}", faceId, memberSourceEntityList.size());
                        }
                        memberRelationRepository.clearSCacheByFace(faceId);
                        faceStatusManager.invalidatePuzzleSourceVersion(faceId);
                        taskTaskService.autoCreateTaskByFaceId(faceId);

                        log.info("自定义人脸匹配完成：faceId={}, 匹配样本数={}, 关联源文件数={}, 免费数={}",
                                faceId, sampleListIds.size(), memberSourceEntityList.size(), freeSourceIds.size());
                    }
                } catch (Exception e) {
                    log.error("处理源文件关联失败，faceId={}", faceId, e);
                }
            } else {
                log.warn("自定义人脸匹配无结果：faceId={}, faceSampleIds={}", faceId, faceSampleIds);
            }

            return mergedResult;

        } catch (BaseException e) {
            throw e;
        } catch (Exception e) {
            log.error("自定义人脸匹配处理异常，faceId={}, faceSampleIds={}", faceId, faceSampleIds, e);
            throw new BaseException("自定义人脸匹配处理失败，请稍后重试");
        }
    }

    @Override
    public void updateRecognition(FaceRecognitionUpdateReq req) {
        if (req == null || req.getFaceId() == null) {
            throw new IllegalArgumentException("faceId 不能为空");
        }
        Long faceId = req.getFaceId();
        FaceEntity face = faceRepository.getFace(faceId);
        if (face == null) {
            throw new BaseException("人脸不存在");
        }

        boolean forceRematch = Boolean.TRUE.equals(req.getForceRematch());
        if (forceRematch) {
            matchFaceId(faceId, false);
            face = faceRepository.getFace(faceId);
        }

        List<Long> currentAccepted = parseMatchSampleIds(face.getMatchSampleIds());
        List<Long> manualAccepted = Optional.ofNullable(req.getManualAcceptedSampleIds()).orElse(Collections.emptyList());

        // 直接使用上传的样本ID列表,不与旧数据合并
        List<Long> finalSampleList = manualAccepted.stream()
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());

        boolean hasManualChange = !manualAccepted.isEmpty();
        boolean needsUpdate = hasManualChange && !finalSampleList.equals(currentAccepted);

        if (needsUpdate) {
            if (finalSampleList.isEmpty()) {
                throw new BaseException("至少需要保留一个样本");
            }
            
            // 设置人工调整标记
            faceMapper.updateManualFlag(faceId, 1);
            faceRepository.clearFaceCache(faceId);
            
            handleCustomFaceMatching(faceId, finalSampleList);
        }

        if (StringUtils.isNotBlank(req.getRemark())) {
            log.info("人脸识别人工调整备注：faceId={}, remark={}", faceId, req.getRemark());
        }
    }

    @Override
    public FaceRecognitionDetailVO getRecognitionDetail(Long faceId) {
        if (faceId == null) {
            throw new IllegalArgumentException("faceId 不能为空");
        }
        FaceEntity face = faceRepository.getFace(faceId);
        if (face == null) {
            throw new BaseException("人脸不存在");
        }

        FaceRecognitionDetailVO detail = new FaceRecognitionDetailVO();
        detail.setFaceId(faceId);
        detail.setMemberId(face.getMemberId());
        detail.setScenicId(face.getScenicId());
        detail.setFaceUrl(face.getFaceUrl());
        detail.setScore(face.getScore());
        detail.setFirstMatchRate(face.getFirstMatchRate() != null ? face.getFirstMatchRate().floatValue() : null);
        detail.setLowThreshold(metricsRecorder.hasLowThreshold(faceId));
        detail.setLastMatchedAt(face.getUpdateAt() != null ? face.getUpdateAt() : face.getCreateAt());

        String matchResultJson = face.getMatchResult();
        if (StringUtils.isBlank(matchResultJson)) {
            detail.setAcceptedSamples(Collections.emptyList());
            detail.setFilteredSamples(Collections.emptyList());
            return detail;
        }

        List<SearchFaceResultItem> resultItems = JacksonUtil.fromJsonToList(matchResultJson, SearchFaceResultItem.class);
        if (resultItems == null) {
            resultItems = Collections.emptyList();
        }

        // 获取景区配置的得分阈值
        Float scoreThreshold = null;
        try {
            ScenicConfigManager configManager = scenicRepository.getScenicConfigManager(face.getScenicId());
            if (configManager != null) {
                BigDecimal thresholdConfig = configManager.getBigDecimal("face_select_score_threshold");
                if (thresholdConfig != null) {
                    // 配置值是0~100的小数，需要转换为0~1的阈值
                    scoreThreshold = thresholdConfig.floatValue() / 100.0f;
                }
            }
        } catch (Exception e) {
            log.warn("获取景区人脸得分阈值配置失败, scenicId: {}, 将不进行得分筛选", face.getScenicId(), e);
        }

        List<Long> persistedAcceptedIds = parseMatchSampleIds(face.getMatchSampleIds());
        LinkedHashSet<Long> sampleUniverse = new LinkedHashSet<>();
        Map<Long, SearchFaceResultItem> itemBySampleId = new LinkedHashMap<>();
        for (SearchFaceResultItem item : resultItems) {
            Long sampleId = parseLongSilently(item.getExtData());
            if (sampleId != null) {
                // 根据得分阈值筛选样本
                if (scoreThreshold != null && item.getScore() != null && item.getScore() < scoreThreshold) {
                    // 得分低于阈值，跳过此样本
                    continue;
                }
                sampleUniverse.add(sampleId);
                itemBySampleId.putIfAbsent(sampleId, item);
            }
        }
        sampleUniverse.addAll(persistedAcceptedIds);

        List<Long> allSampleIds = new ArrayList<>(sampleUniverse);
        if (allSampleIds.isEmpty()) {
            detail.setAcceptedSamples(Collections.emptyList());
            detail.setFilteredSamples(Collections.emptyList());
            return detail;
        }

        List<SourceEntity> sourceEntities = sourceMapper.listBySampleIds(allSampleIds);
        Map<Long, SourceEntity> sourceBySampleId = sourceEntities.stream()
                .collect(Collectors.toMap(SourceEntity::getFaceSampleId, Function.identity(), (a, b) -> a, LinkedHashMap::new));
        Map<Long, SourceEntity> sourceById = sourceEntities.stream()
                .collect(Collectors.toMap(SourceEntity::getId, Function.identity(), (a, b) -> a));

        List<MemberSourceEntity> relations = new ArrayList<>();
        List<MemberSourceEntity> videoRelations = memberRelationRepository.listSourceByFaceRelation(faceId, 1);
        if (videoRelations != null) {
            relations.addAll(videoRelations);
        }
        List<MemberSourceEntity> imageRelations = memberRelationRepository.listSourceByFaceRelation(faceId, 2);
        if (imageRelations != null) {
            relations.addAll(imageRelations);
        }
        Map<Long, MemberSourceEntity> relationBySampleId = new HashMap<>();
        for (MemberSourceEntity relation : relations) {
            SourceEntity source = sourceById.get(relation.getSourceId());
            if (source != null && source.getFaceSampleId() != null) {
                relationBySampleId.putIfAbsent(source.getFaceSampleId(), relation);
            }
        }

        Map<Long, DeviceEntity> deviceCache = new HashMap<>();

        Set<Long> persistedAcceptedSet = new LinkedHashSet<>(persistedAcceptedIds);
        List<Long> acceptedOrdered = new ArrayList<>();
        for (Long sampleId : allSampleIds) {
            if (persistedAcceptedSet.contains(sampleId)) {
                acceptedOrdered.add(sampleId);
            }
        }
        for (Long sampleId : persistedAcceptedIds) {
            if (!acceptedOrdered.contains(sampleId)) {
                acceptedOrdered.add(sampleId);
            }
        }

        List<FaceRecognitionSampleVO> acceptedSamples = acceptedOrdered.stream()
                .map(sampleId -> buildSampleVO(
                        sampleId,
                        true,
                        itemBySampleId.get(sampleId),
                        sourceBySampleId.get(sampleId),
                        deviceCache))
                .collect(Collectors.toList());

        Set<Long> acceptedSet = new LinkedHashSet<>(acceptedOrdered);
        List<FaceRecognitionSampleVO> filteredSamples = new ArrayList<>();
        for (Long sampleId : allSampleIds) {
            if (acceptedSet.contains(sampleId)) {
                continue;
            }
            FaceRecognitionSampleVO sampleVO = buildSampleVO(
                    sampleId,
                    false,
                    itemBySampleId.get(sampleId),
                    sourceBySampleId.get(sampleId),
                    deviceCache);
            if (sampleVO.getSourceId() != null) {
                filteredSamples.add(sampleVO);
            }
        }

        detail.setAcceptedSamples(acceptedSamples);
        detail.setFilteredSamples(filteredSamples);
        return detail;
    }

    private List<Long> parseMatchSampleIds(String matchSampleIds) {
        if (StringUtils.isBlank(matchSampleIds)) {
            return Collections.emptyList();
        }
        String[] segments = matchSampleIds.split(",");
        List<Long> result = new ArrayList<>(segments.length);
        for (String segment : segments) {
            Long id = parseLongSilently(segment);
            if (id != null) {
                result.add(id);
            }
        }
        return result;
    }

    private Long parseLongSilently(String value) {
        if (StringUtils.isBlank(value)) {
            return null;
        }
        try {
            return Long.valueOf(value.trim());
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private FaceRecognitionSampleVO buildSampleVO(Long sampleId,
                                                  boolean accepted,
                                                  SearchFaceResultItem resultItem,
                                                  SourceEntity sourceEntity,
                                                  Map<Long, DeviceEntity> deviceCache) {
        FaceRecognitionSampleVO vo = new FaceRecognitionSampleVO();
        vo.setSampleId(sampleId);
        vo.setAccepted(accepted);
        if (resultItem != null) {
            vo.setScore(resultItem.getScore());
        }
        if (sourceEntity != null) {
            if (sourceEntity.getDeviceId() != null) {
                vo.setDeviceId(sourceEntity.getDeviceId());
                vo.setShotAt(sourceEntity.getCreateTime());
                DeviceEntity device = getDeviceCached(sourceEntity.getDeviceId(), deviceCache);
                if (device != null) {
                    vo.setDeviceName(device.getName());
                }
            }
            vo.setSourceId(sourceEntity.getId());
            vo.setSourceUrl(resolveSourceUrl(sourceEntity));
        }
        return vo;
    }

    private DeviceEntity getDeviceCached(Long deviceId, Map<Long, DeviceEntity> cache) {
        if (deviceId == null) {
            return null;
        }
        if (cache.containsKey(deviceId)) {
            return cache.get(deviceId);
        }
        DeviceEntity device = deviceRepository.getDevice(deviceId);
        cache.put(deviceId, device);
        return device;
    }

    private String resolveSourceUrl(SourceEntity sourceEntity) {
        if (sourceEntity == null) {
            return null;
        }
        if (!StringUtils.isBlank(sourceEntity.getThumbUrl())) {
            return sourceEntity.getThumbUrl();
        }
        if (!StringUtils.isBlank(sourceEntity.getUrl())) {
            return sourceEntity.getUrl();
        }
        return null;
    }
}
