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

import cn.hutool.core.date.DateUtil;
import cn.hutool.http.HttpUtil;
import com.ycwl.basic.biz.FaceStatusManager;
import com.ycwl.basic.enums.FaceCutStatus;
import com.ycwl.basic.enums.FacePieceUpdateStatus;
import com.ycwl.basic.enums.TemplateRenderStatus;
import com.ycwl.basic.enums.VideoTaskStatus;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
import com.ycwl.basic.repository.MemberRelationRepository;
import com.ycwl.basic.repository.OrderRepository;
import com.ycwl.basic.service.pc.FaceService;
import com.ycwl.basic.utils.JacksonUtil;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.constant.StorageConstant;
import com.ycwl.basic.image.watermark.ImageWatermarkFactory;
import com.ycwl.basic.image.watermark.edge.WatermarkEdgeService;
import com.ycwl.basic.image.watermark.entity.WatermarkInfo;
import com.ycwl.basic.image.watermark.enums.ImageWatermarkOperatorEnum;
import com.ycwl.basic.image.watermark.exception.ImageWatermarkException;
import com.ycwl.basic.image.watermark.operator.IOperator;
import com.ycwl.basic.mapper.*;
import com.ycwl.basic.model.mobile.goods.*;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.mobile.order.PriceObj;
import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.pc.source.entity.SourceWatermarkEntity;
import com.ycwl.basic.model.pc.source.req.SourceReqQuery;
import com.ycwl.basic.model.pc.source.resp.SourceRespVO;
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.pc.video.req.VideoReqQuery;
import com.ycwl.basic.model.pc.video.resp.VideoRespVO;
import com.ycwl.basic.repository.DeviceRepository;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.repository.VideoRepository;
import com.ycwl.basic.repository.VideoTaskRepository;
import com.ycwl.basic.service.mobile.GoodsService;
import com.ycwl.basic.repository.TemplateRepository;
import com.ycwl.basic.config.VideoUpdateConfig;
import com.ycwl.basic.model.repository.TaskUpdateResult;
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.enums.StorageType;
import com.ycwl.basic.utils.ApiResponse;
import com.ycwl.basic.utils.WxMpUtil;
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.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @Author：longbinbin
 * @Date：2024/12/5 15:04
 */
@Slf4j
@Service
public class GoodsServiceImpl implements GoodsService {
    @Autowired
    private VideoMapper videoMapper;
    @Autowired
    private SourceMapper sourceMapper;
    @Autowired
    private FaceMapper faceMapper;
    @Autowired
    private TemplateRepository templateRepository;
    @Autowired
    private VideoRepository videoRepository;
    @Autowired
    private VideoTaskRepository videoTaskRepository;
    @Autowired
    private ScenicRepository scenicRepository;
    @Autowired
    private OrderBiz orderBiz;
    @Autowired
    private FaceRepository faceRepository;
    @Autowired
    private DeviceRepository deviceRepository;
    @Autowired
    private VideoUpdateConfig videoUpdateConfig;
    @Autowired
    private MemberRelationRepository memberRelationRepository;
    @Autowired
    private PrinterMapper printerMapper;
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private FaceStatusManager faceStatusManager;
    @Autowired
    private WatermarkEdgeService watermarkEdgeService;
    @Autowired
    @Lazy
    private FaceService faceService;

    @Override
    public List<GoodsDetailVO> sourceGoodsList(GoodsReqQuery query) {
        FaceEntity face = faceRepository.getFace(query.getFaceId());
        if (face == null) {
            return Collections.emptyList();
        }
        Integer sourceType = query.getSourceType();
        SourceReqQuery sourceReqQuery = new SourceReqQuery();
        sourceReqQuery.setScenicId(face.getScenicId());
        sourceReqQuery.setIsBuy(query.getIsBuy());
        sourceReqQuery.setType(sourceType);
        sourceReqQuery.setFaceId(face.getId());
        List<SourceRespVO> list = sourceMapper.listUser(sourceReqQuery);
        List<GoodsDetailVO> goodsDetailVOList = new ArrayList<>();

        String goodsNamePrefix;
        if (sourceType == 1) {
            goodsNamePrefix = "录像";
        } else if (sourceType == 2) {
            goodsNamePrefix = "图片";
        } else {
            goodsNamePrefix = "其他类型";
        }
        //图片编号
        int i=1;
        for (SourceRespVO sourceRespVO : list) {
            GoodsDetailPrintSceneVO goodsDetailVO = new GoodsDetailPrintSceneVO();
            goodsDetailVO.setFaceId(sourceRespVO.getFaceId());
            goodsDetailVO.setGoodsId(sourceRespVO.getId());
            String shootingTime = DateUtil.format(sourceRespVO.getCreateTime(), "yyyy.MM.dd HH:mm:ss");
            if (Integer.valueOf(3).equals(sourceType)) {
                goodsDetailVO.setGoodsName("拍摄时间：" + shootingTime);
            } else {
                if (i < 10) {
                    goodsDetailVO.setGoodsName(goodsNamePrefix + "0" + i + " " + shootingTime);
                } else {
                    goodsDetailVO.setGoodsName(goodsNamePrefix + i + " " + shootingTime);
                }
            }
            goodsDetailVO.setScenicId(sourceRespVO.getScenicId());
            try {
                ScenicV2DTO scenic = scenicRepository.getScenicBasic(sourceRespVO.getScenicId());
                goodsDetailVO.setScenicName(scenic.getName());
            } catch (Exception e) {
                goodsDetailVO.setScenicName("");
            }
            if (StringUtils.isNotBlank(sourceRespVO.getThumbUrl())) {
                goodsDetailVO.setTemplateCoverUrl(sourceRespVO.getThumbUrl());
            } else {
                goodsDetailVO.setTemplateCoverUrl(sourceRespVO.getUrl());
            }
            goodsDetailVO.setGoodsType(sourceType);
            goodsDetailVO.setFaceId(face.getId());
            goodsDetailVO.setGoodsId(sourceRespVO.getId());
            goodsDetailVO.setIsFree(sourceRespVO.getIsFree());
            goodsDetailVO.setIsBuy(sourceRespVO.getIsBuy());
            goodsDetailVO.setVideoUrl(sourceRespVO.getVideoUrl());
            goodsDetailVO.setUrl(sourceRespVO.getUrl());
            goodsDetailVO.setCreateTime(sourceRespVO.getCreateTime());
            goodsDetailVO.setIsFree(sourceRespVO.getIsFree());
            if (Strings.CI.equals("print", query.getScene())) {
                // 查询该素材是否在用户打印列表中
                goodsDetailVO.setInList(printerMapper.countFacePhoto(sourceRespVO.getScenicId(), face.getId(), sourceRespVO.getId()) > 0);
            }
            goodsDetailVOList.add(goodsDetailVO);
            i++;
        }

        return goodsDetailVOList;
    }

    @Override
    public ApiResponse<VideoGoodsDetailVO> videoGoodsDetail(Long userId, Long videoId) {
        VideoGoodsDetailVO goodsDetailVO = new VideoGoodsDetailVO();
        VideoEntity video = videoRepository.getVideo(videoId);
        if (video == null) {
            return ApiResponse.fail("该vlog不存在或已失效");
        }
        TemplateRespVO template = templateRepository.getTemplate(video.getTemplateId());
        goodsDetailVO.setGoodsName(template.getName());
        goodsDetailVO.setScenicId(video.getScenicId());
        try {
            ScenicV2DTO scenic = scenicRepository.getScenicBasic(video.getScenicId());
            goodsDetailVO.setScenicName(scenic.getName());
        } catch (Exception e) {
            goodsDetailVO.setScenicName("");
        }
        goodsDetailVO.setGoodsType(0);
        goodsDetailVO.setGoodsId(video.getId());
        goodsDetailVO.setVideoUrl(video.getVideoUrl());
        goodsDetailVO.setTemplateId(video.getTemplateId());
        goodsDetailVO.setTemplateCoverUrl(template.getCoverUrl());
        goodsDetailVO.setCreateTime(video.getCreateTime());
        goodsDetailVO.setHeight(video.getHeight());
        goodsDetailVO.setWidth(video.getWidth());
        goodsDetailVO.setDuration(video.getDuration());
        if (userId == null) {
            goodsDetailVO.setIsBuy(0);
            goodsDetailVO.setShare(true);
            goodsDetailVO.setPrice("未登录");
        } else {
            VideoEntity entity = videoMapper.getEntity(videoId);
            goodsDetailVO.setFaceId(entity.getFaceId());
            boolean isBuy = orderRepository.checkUserBuyFaceItem(userId, entity.getFaceId(), 0, videoId);
            if (!isBuy) {
                isBuy = orderRepository.checkUserBuyFaceItem(userId, entity.getFaceId(), -1, entity.getTemplateId());
            }
            if (!isBuy) {
                PriceObj priceObj = orderBiz.queryPrice(video.getScenicId(), userId, 0, videoId);
                if (priceObj.isFree()) {
                    goodsDetailVO.setIsBuy(1);
                } else {
                    goodsDetailVO.setIsBuy(0);
                    goodsDetailVO.setPrice(priceObj.getPrice().toString());
                    goodsDetailVO.setOrigPrice(priceObj.getPrice());
                    goodsDetailVO.setSlashPrice(priceObj.getSlashPrice());
                }
            } else {
                goodsDetailVO.setIsBuy(1);
            }
        }
        goodsDetailVO.setShotTime(videoTaskRepository.getTaskShotDate(video.getTaskId()));
        goodsDetailVO.setLensNum(videoTaskRepository.getTaskLensNum(video.getTaskId()));
        goodsDetailVO.setDevicesNum(videoTaskRepository.getTaskDeviceNum(video.getTaskId()));

        return ApiResponse.success(goodsDetailVO);
    }

    /**
     * 查询用户人脸对应的视频合成任务状态
     *
     * <p>业务流程：
     * <ol>
     *   <li>验证人脸是否存在</li>
     *   <li>检查视频切片状态（CUTTING → 返回处理中）</li>
     *   <li>遍历景区模板，检查渲染状态（RENDERING → 返回处理中）</li>
     *   <li>统计已渲染完成的模板数量</li>
     *   <li>根据切片状态返回最终结果：
     *     <ul>
     *       <li>WAITING_USER_SELECT → 处理中（等待用户选择模板）</li>
     *       <li>COMPLETED → 成功（查询已完成的视频信息）</li>
     *     </ul>
     *   </li>
     * </ol>
     *
     * @param faceId 人脸ID
     * @return 视频任务状态VO，包含状态码、已完成数量、视频信息等
     *         <ul>
     *           <li>status = -1: 人脸不存在</li>
     *           <li>status = 2: 处理中（切片中/渲染中/等待选择）</li>
     *           <li>status = 1: 成功（已有完成的视频）</li>
     *         </ul>
     */
    @Override
    public VideoTaskStatusVO getTaskStatusByFaceId(Long faceId) {
        VideoTaskStatusVO response = new VideoTaskStatusVO();

        // ==================== 第一步：验证人脸是否存在 ====================
        FaceEntity face = faceRepository.getFace(faceId);
        if (face == null) {
            // 人脸不存在，返回无效人脸状态
            response.setStatus(VideoTaskStatus.INVALID_FACE.getCode());
            return response;
        }

        // 设置基本信息
        response.setFaceId(faceId);
        response.setScenicId(face.getScenicId());

        // ==================== 第二步：检查视频切片状态 ====================
        // 从缓存中获取切片状态（CUTTING/COMPLETED/WAITING_USER_SELECT）
        FaceCutStatus status = faceStatusManager.getFaceCutStatus(faceId);
        response.setCutStatus(status.getCode());

        if (status == FaceCutStatus.CUTTING) {
            // 视频片段正在切割中，用户需要等待切片完成
            // 前端展示：「正在检索新的视频片段」
            response.setStatus(VideoTaskStatus.PROCESSING.getCode());
            return response;
        }

        ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
        if (Integer.valueOf(2).equals(scenicConfig.getInteger("scenic_mode", 0))) {
            // 摄影师拍照
            List<MemberSourceEntity> list = memberRelationRepository.listSourceByFaceRelation(faceId, 2);
            response.setStatus(VideoTaskStatus.SUCCESS.getCode());
            response.setCount(list.size());
            return response;
        }

        // ==================== 第三步：检查模板渲染状态 ====================
        // 获取该景区的所有视频模板
        List<TemplateRespVO> templateList = templateRepository.getTemplateListByScenicId(response.getScenicId());
        AtomicInteger count = new AtomicInteger();

        // 遍历所有模板，检查每个模板的渲染状态
        for (TemplateRespVO template : templateList) {
            // 从缓存中获取该人脸在该模板下的渲染状态
            TemplateRenderStatus renderStatus = faceStatusManager.getTemplateRenderStatus(faceId, template.getId());

            if (renderStatus == TemplateRenderStatus.RENDERING) {
                // 发现有模板正在渲染中，立即返回处理中状态
                // 前端展示：「专属视频合成中」
                response.setTemplateId(template.getId());
                response.setCount(0);
                response.setStatus(VideoTaskStatus.PROCESSING.getCode());
                return response;
            }

            if (renderStatus == TemplateRenderStatus.RENDERED) {
                // 统计已渲染完成的模板数量
                count.incrementAndGet();
            }
        }

        // 设置已完成数量和总模板数
        response.setCount(count.get());
        response.setMaxCount(templateList.size());
        // 检查是否有完成的模板，如果没有则返回待处理
        if (count.get() <= 0) {
            response.setStatus(VideoTaskStatus.PENDING.getCode());
            return response;
        }

        // ==================== 第四步：根据切片完成状态返回结果 ====================

        if (status == FaceCutStatus.WAITING_USER_SELECT || status == FaceCutStatus.COMPLETED) {
            // 切片已完成（或等待用户选择），查询该人脸关联的视频信息
            List<MemberVideoEntity> taskList = videoMapper.listRelationByFace(faceId);
            if (taskList == null || taskList.isEmpty()) {
                response.setStatus(VideoTaskStatus.PENDING.getCode());
                return response;
            }
            if (taskList.getLast().getVideoId() == null) {
                response.setStatus(VideoTaskStatus.PROCESSING.getCode());
                return response;
            }

            // 设置最新的视频信息（取最后一个）
            response.setTaskId(taskList.getLast().getTaskId());
            response.setTemplateId(taskList.getLast().getTemplateId());
            response.setVideoId(taskList.getLast().getVideoId());

            // 返回成功状态
            // 前端展示：「AI已为您合成{count}个视频」
            response.setStatus(VideoTaskStatus.SUCCESS.getCode());
            return response;
        }

        // 兜底返回（理论上不应该走到这里）
        return response;
    }

    @Override
    public VideoTaskStatusVO getAllTaskStatus(Long userId) {
        FaceRespVO lastFaceByUserId = faceMapper.findLastFaceByUserId(String.valueOf(userId));
        return getTaskStatusByFaceId(lastFaceByUserId.getId());
    }

    /**
     * 查询指定人脸和模板的视频合成任务状态
     *
     * <p>业务流程：
     * <ol>
     *   <li>验证人脸是否存在</li>
     *   <li>检查视频切片状态（CUTTING → 返回处理中）</li>
     *   <li>检查指定模板的渲染状态（RENDERING → 返回处理中）</li>
     *   <li>检查是否等待用户选择（WAITING_USER_SELECT → 返回处理中）</li>
     *   <li>查询该模板对应的视频信息并返回成功状态</li>
     * </ol>
     *
     * @param faceId 人脸ID
     * @param templateId 模板ID
     * @return 视频任务状态VO，包含该模板的状态信息
     *         <ul>
     *           <li>status = -1: 人脸不存在</li>
     *           <li>status = 0: 待制作（无任务记录）</li>
     *           <li>status = 2: 处理中（切片中/渲染中/等待选择）</li>
     *           <li>status = 1: 成功（该模板视频已完成）</li>
     *         </ul>
     */
    @Override
    public VideoTaskStatusVO getTaskStatusByTemplateId(Long faceId, Long templateId) {
        VideoTaskStatusVO response = new VideoTaskStatusVO();
        response.setFaceId(faceId);
        response.setTemplateId(templateId);

        // ==================== 第一步：验证人脸是否存在 ====================
        FaceEntity face = faceRepository.getFace(faceId);
        if (face == null) {
            // 人脸不存在，返回无效人脸状态
            response.setStatus(VideoTaskStatus.INVALID_FACE.getCode());
            return response;
        }

        // 设置景区信息
        response.setScenicId(face.getScenicId());

        // ==================== 第二步：检查视频切片状态 ====================
        // 从缓存中获取切片状态
        FaceCutStatus cutStatus = faceStatusManager.getFaceCutStatus(faceId);
        response.setCutStatus(cutStatus.getCode());

        if (cutStatus == FaceCutStatus.CUTTING) {
            // 视频片段正在切割中，无法生成任何视频
            // 前端展示：「正在检索新的视频片段」
            response.setStatus(VideoTaskStatus.PROCESSING.getCode());
            return response;
        }

        // ==================== 第三步：检查指定模板的渲染状态 ====================
        // 从缓存中获取该人脸在该模板下的渲染状态
        TemplateRenderStatus renderStatus = faceStatusManager.getTemplateRenderStatus(faceId, templateId);

        if (renderStatus == TemplateRenderStatus.RENDERING) {
            // 该模板正在渲染中
            // 前端展示：「专属视频合成中」
            response.setStatus(VideoTaskStatus.PROCESSING.getCode());
            return response;
        }

        if (renderStatus == TemplateRenderStatus.NONE) {
            // 该模板从未开始渲染
            // 可能是用户还没选择该模板，或系统还未开始处理
            response.setStatus(VideoTaskStatus.PENDING.getCode());
            return response;
        }

        // ==================== 第四步：检查是否等待用户选择 ====================
        if (cutStatus == FaceCutStatus.WAITING_USER_SELECT && renderStatus != TemplateRenderStatus.RENDERED) {
            // 切片已完成，但需要等待用户手动选择模板
            // 前端展示：「专属视频合成中」
            response.setStatus(VideoTaskStatus.PROCESSING.getCode());
            return response;
        }

        // ==================== 第五步：查询已完成的视频信息 ====================
        if (renderStatus == TemplateRenderStatus.RENDERED) {
            // 该模板已渲染完成，查询对应的视频信息
            List<MemberVideoEntity> taskList = videoMapper.listRelationByFaceAndTemplate(faceId, templateId);

            if (taskList == null || taskList.isEmpty() || taskList.getLast().getVideoId() == null) {
                // 理论上不应该出现：渲染完成但无视频记录
                // 可能是数据不一致，返回待制作状态
                response.setStatus(VideoTaskStatus.PROCESSING.getCode());
                return response;
            }

            // 获取最新的视频记录（取最后一个）
            MemberVideoEntity lastVideo = taskList.getLast();
            response.setTaskId(lastVideo.getTaskId());
            response.setVideoId(lastVideo.getVideoId());
            response.setCount(1); // 该模板已完成1个视频

            // 返回成功状态
            // 前端展示：「AI已为您合成视频」
            response.setStatus(VideoTaskStatus.SUCCESS.getCode());
            return response;
        }

        // 兜底：默认返回待制作状态
        response.setStatus(VideoTaskStatus.PENDING.getCode());
        return response;
    }

    @Override
    public VideoTaskStatusVO getTaskStatusByScenicId(Long userId, Long scenicId) {
        FaceRespVO faceVO = faceMapper.getLatestByMemberId(userId, scenicId);
        VideoTaskStatusVO response = new VideoTaskStatusVO();
        response.setScenicId(scenicId);
        if (faceVO == null) {
            // 从来没露脸
            response.setStatus(-2);
            return response;
        }
        return getTaskStatusByFaceId(faceVO.getId());
    }

    @Override
    public ApiResponse<GoodsDetailVO> sourceGoodsInfo(Long sourceId) {
        SourceRespVO sourceRespVO = sourceMapper.getById(sourceId);
        if (sourceRespVO == null) {
            return ApiResponse.fail("该视频不存在");
        }
        GoodsDetailVO goodsDetailVO = new GoodsDetailVO();
        goodsDetailVO.setGoodsName("原片");
        goodsDetailVO.setScenicId(sourceRespVO.getScenicId());
        try {
            ScenicV2DTO scenic = scenicRepository.getScenicBasic(sourceRespVO.getScenicId());
            goodsDetailVO.setScenicName(scenic.getName());
        } catch (Exception e) {
            goodsDetailVO.setScenicName("");
        }
        goodsDetailVO.setGoodsType(sourceRespVO.getType());
        goodsDetailVO.setGoodsId(sourceRespVO.getId());
        goodsDetailVO.setVideoUrl(sourceRespVO.getVideoUrl());
        goodsDetailVO.setTemplateCoverUrl(sourceRespVO.getUrl());
        goodsDetailVO.setCreateTime(sourceRespVO.getCreateTime());
        return ApiResponse.success(goodsDetailVO);
    }

    @Override
    public List<GoodsUrlVO> sourceGoodsListDownload(GoodsReqQuery query) {
        List<File> tmpFile = new ArrayList<>();
        FaceEntity face = faceRepository.getFace(query.getFaceId());
        if (face == null) {
            return Collections.emptyList();
        }
        Integer sourceType = query.getSourceType();
        SourceReqQuery sourceReqQuery = new SourceReqQuery();
        sourceReqQuery.setScenicId(face.getScenicId());
        sourceReqQuery.setType(sourceType);
        sourceReqQuery.setFaceId(query.getFaceId());
        List<SourceRespVO> list = sourceMapper.listUser(sourceReqQuery);
        if (query.getGoodsId() != null) {
            list = list.stream().filter(source -> source.getId().equals(query.getGoodsId())).toList();
        }
        if (Integer.valueOf(1).equals(query.getSourceType())) {
            return list.stream().map(source -> {
                GoodsUrlVO goodsUrlVO = new GoodsUrlVO();
                goodsUrlVO.setGoodsType(source.getType());
                goodsUrlVO.setGoodsId(source.getId());
                goodsUrlVO.setUrl(source.getVideoUrl());
                goodsUrlVO.setCreateTime(source.getCreateTime());
                return goodsUrlVO;
            }).collect(Collectors.toList());
        }
        long count = list.stream().filter((item) -> {
            if (item.getIsFree() > 0) {
                return false;
            }
            if (item.getIsBuy() > 0) {
                return false;
            }
            return true;
        }).count();
        IsBuyRespVO isBuy = orderBiz.isBuy(face.getScenicId(), face.getMemberId(), face.getId(), sourceType == 3 ? 13 : sourceType, face.getId());
        if (count > 0) {
            if (!isBuy.isBuy()) {
                return Collections.emptyList();
            }
        }
        List<GoodsUrlVO> defaultUrlList = list.stream().map(source -> {
            GoodsUrlVO goodsUrlVO = new GoodsUrlVO();
            goodsUrlVO.setGoodsType(source.getType());
            goodsUrlVO.setGoodsId(source.getId());
            goodsUrlVO.setUrl(source.getUrl());
            goodsUrlVO.setCreateTime(source.getCreateTime());
            return goodsUrlVO;
        }).collect(Collectors.toList());
        ScenicConfigManager configManager = scenicRepository.getScenicConfigManager(face.getScenicId());
        MpConfigEntity scenicMpConfig = scenicRepository.getScenicMpConfig(face.getScenicId());
        if (scenicMpConfig == null) {
            log.warn("未配置小程序参数，无法生成二维码");
            return defaultUrlList;
        }
        if (configManager != null && configManager.getString("watermark_type") != null && !isBuy.isBuy()) {
            ImageWatermarkOperatorEnum type = ImageWatermarkOperatorEnum.getByCode(configManager.getString("watermark_type"));
            if (type != null) {
                IStorageAdapter adapter;
                if (configManager.getEnum("store_type", StorageType.class) != null) {
                    adapter = StorageFactory.get(configManager.getEnum("store_type", StorageType.class));
                    adapter.loadConfig(JacksonUtil.parseObject(configManager.getString("store_config_json"), Map.class));
                } else {
                    adapter = StorageFactory.use("assets-ext");
                }
                List<SourceWatermarkEntity> watermarkEntityList = sourceMapper.listSourceWatermark(list.stream().map(SourceRespVO::getId).collect(Collectors.toList()), face.getId(), type.getType());

                // 边缘端处理：需要二维码和头像 URL
                String qrcodeUrl = faceService.bindWxaCode(face.getId());
                String faceUrlForEdge = face.getFaceUrl();

                final String finalQrcodeUrl = qrcodeUrl;
                final String finalFaceUrl = faceUrlForEdge;
                final String dtFormat = configManager.getString("watermark_dt_format");

                List<GoodsUrlVO> collect = defaultUrlList.stream().peek(item -> {
                    Optional<SourceWatermarkEntity> any = watermarkEntityList.stream()
                            .filter(watermark -> watermark.getSourceId().equals(item.getGoodsId()))
                            .findAny();
                    if (any.isPresent()) {
                        item.setUrl(any.get().getWatermarkUrl());
                    } else {
                        // 获取景区文字
                        String text = configManager.getString("watermark_scenic_text");
                        if (StringUtils.isBlank(text)) {
                            SourceEntity entity = sourceMapper.getEntity(item.getGoodsId());
                            if (entity != null) {
                                DeviceEntity device = deviceRepository.getDevice(entity.getDeviceId());
                                if (device != null) {
                                    text = device.getName();
                                }
                            }
                        }

                        // 尝试边缘端处理
                        String resultUrl = watermarkEdgeService.processWatermark(
                                type,
                                item.getUrl(),
                                finalQrcodeUrl,
                                finalFaceUrl,
                                text,
                                item.getCreateTime(),
                                dtFormat,
                                item.getGoodsId(),
                                face.getId()
                        );
                        if (resultUrl != null) {
                            sourceMapper.addSourceWatermark(item.getGoodsId(), face.getId(), type.getType(), resultUrl);
                            item.setUrl(resultUrl);
                            return;
                        }
                        log.warn("边缘端水印处理失败，降级到本地处理: sourceId={}", item.getGoodsId());

                        // 本地处理（边缘端处理失败时的降级）
                        File dstFile = new File(item.getGoodsId() + ".jpg");
                        File watermarkedFile = new File(item.getGoodsId() + "_" + type.getType() + "." + type.getPreferFileType());
                        try {
                            HttpUtil.downloadFile(item.getUrl().replace("oss.zhentuai.com", "frametour-assets.oss-cn-shanghai-internal.aliyuncs.com"), dstFile);
                        } catch (Exception e) {
                            log.error("downloadFile error", e);
                            return;
                        }
                        tmpFile.add(dstFile);

                        // 降级处理时下载头像文件
                        File localFaceFile = new File("face_fallback_" + face.getId() + ".jpg");
                        try {
                            HttpUtil.downloadFile(face.getFaceUrl().replace("oss.zhentuai.com", "frametour-assets.oss-cn-shanghai-internal.aliyuncs.com"), localFaceFile);
                            tmpFile.add(localFaceFile);
                        } catch (Exception e) {
                            log.error("download face for fallback error", e);
                            localFaceFile = null;
                        }

                        WatermarkInfo info = new WatermarkInfo();
                        info.setOriginalFile(dstFile);
                        info.setQrcodeFile(null);
                        info.setScenicLine(text);
                        info.setDatetime(item.getCreateTime());
                        info.setFaceFile(localFaceFile);
                        info.setDtFormat(dtFormat);
                        info.setWatermarkedFile(watermarkedFile);
                        try {
                            IOperator fallbackOperator = ImageWatermarkFactory.get(type);
                            fallbackOperator.process(info);
                        } catch (ImageWatermarkException e) {
                            log.error("process error", e);
                            return;
                        }
                        String url = adapter.uploadFile(null, watermarkedFile, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
                        adapter.setAcl(StorageAcl.PUBLIC_READ, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName());
                        sourceMapper.addSourceWatermark(item.getGoodsId(), face.getId(), type.getType(), url);
                        tmpFile.add(watermarkedFile);
                        item.setUrl(url);
                    }
                }).collect(Collectors.toList());
                for (File file : tmpFile) {
                    file.delete();
                }
                return collect;
            }
        }
        return defaultUrlList;
    }

    @Override
    public Integer sourceGoodsCount(GoodsReqQuery query) {
        FaceEntity face = faceRepository.getFace(query.getFaceId());
        if (face == null) {
            return 0;
        }
        List<MemberSourceEntity> memberSourceEntities = memberRelationRepository.listSourceByFaceRelation(face.getId(), query.getSourceType());
        return memberSourceEntities.size();
    }

    @Override
    public VideoUpdateCheckVO checkVideoUpdate(Long videoId) {
        VideoUpdateCheckVO result = new VideoUpdateCheckVO();
        result.setVideoId(videoId);

        if (!videoUpdateConfig.isEnabled()) {
            log.info("视频更新检查功能已禁用");
            result.setCanUpdate(false);
            return result;
        }

        VideoEntity video = videoRepository.getVideo(videoId);
        if (video == null) {
            log.error("视频不存在: videoId={}", videoId);
            result.setCanUpdate(false);
            return result;
        }

        FacePieceUpdateStatus updateStatus = faceStatusManager.getFacePieceUpdateStatus(video.getFaceId(), video.getTemplateId());
        if (updateStatus == FacePieceUpdateStatus.NO_NEW_PIECES) {
            result.setCanUpdate(false);
            return result;
        }

        Long taskId = video.getTaskId();
        if (taskId == null) {
            log.warn("视频没有关联任务: videoId={}", videoId);
            result.setCanUpdate(false);
            return result;
        }

        result.setTaskId(taskId);
        result.setFaceId(video.getFaceId());
        result.setTemplateId(video.getTemplateId());

        TaskUpdateResult taskResult = videoTaskRepository.checkTaskUpdate(taskId);
        if (!taskResult.isCanUpdate()) {
            faceStatusManager.markNoNewPieces(video.getFaceId(), video.getTemplateId());
        }
        result.setCanUpdate(taskResult.isCanUpdate());
        result.setNewSegmentCount(taskResult.getNewSegmentCount());
        result.setTotalSegmentCount(taskResult.getTotalSegmentCount());
        result.setOriginalSegmentCount(taskResult.getOriginalSegmentCount());

        log.debug("视频更新检查完成: videoId={}, taskId={}, canUpdate={}, newSegmentCount={}",
                videoId, taskId, result.isCanUpdate(), result.getNewSegmentCount());

        return result;
    }

    @Override
    public ApiResponse<List<GoodsPageVO>> listGoodsByFaceIdList(List<Long> faceIds, Integer isBuy, Long scenicId) {
        // 参数校验
        if (faceIds == null || faceIds.isEmpty()) {
            return ApiResponse.success(Collections.emptyList());
        }
        if (scenicId == null) {
            return ApiResponse.success(Collections.emptyList());
        }

        // 获取景区配置
        ScenicConfigManager configManager = scenicRepository.getScenicConfigManager(scenicId);

        // 使用 LinkedHashMap 按 goodsType-goodsId 去重
        Map<String, GoodsPageVO> goodsMap = new LinkedHashMap<>();

        // 循环查询每个 faceId 的商品
        for (Long faceId : faceIds) {
            // 查询成片 vlog (goodsType = 0)
            VideoReqQuery videoReqQuery = new VideoReqQuery();
            videoReqQuery.setScenicId(scenicId);
            videoReqQuery.setIsBuy(isBuy);
            videoReqQuery.setFaceId(faceId);

            List<VideoRespVO> videoList = videoMapper.queryByRelation(videoReqQuery);
            for (VideoRespVO videoRespVO : videoList) {
                String key = "0-" + videoRespVO.getId(); // goodsType=0, goodsId=videoId
                if (!goodsMap.containsKey(key)) {
                    GoodsPageVO goodsPageVO = new GoodsPageVO();
                    goodsPageVO.setGoodsName(videoRespVO.getTemplateName());
                    goodsPageVO.setScenicId(videoRespVO.getScenicId());
                    try {
                        ScenicV2DTO scenic = scenicRepository.getScenicBasic(videoRespVO.getScenicId());
                        goodsPageVO.setScenicName(scenic.getName());
                    } catch (Exception e) {
                        goodsPageVO.setScenicName("");
                    }
                    goodsPageVO.setGoodsType(0);
                    goodsPageVO.setFaceId(videoRespVO.getFaceId());
                    goodsPageVO.setGoodsId(videoRespVO.getId());
                    goodsPageVO.setTemplateName(videoRespVO.getTemplateName());
                    goodsPageVO.setTemplateCoverUrl(videoRespVO.getTemplateCoverUrl());
                    goodsMap.put(key, goodsPageVO);
                }
            }
        }

        // 查询源素材 (goodsType = 1/2) - 使用新的 GROUP BY 方法
        SourceReqQuery sourceReqQuery = new SourceReqQuery();
        sourceReqQuery.setScenicId(scenicId);
        sourceReqQuery.setIsBuy(isBuy);
        sourceReqQuery.setFaceIds(faceIds);

        // 使用 queryGroupedByFaceAndType 方法,数据库已经按 faceId+type 分组
        List<SourceRespVO> sourceList = sourceMapper.queryGroupedByFaceAndType(sourceReqQuery);

        // 遍历分组后的结果,每个 faceId+type 组合只有一条记录
        for (SourceRespVO source : sourceList) {
            Integer type = source.getType();
            Long sourceFaceId = source.getFaceId();

            // 根据景区配置过滤禁用的素材类型
            boolean isDisabled = false;
            if (Integer.valueOf(1).equals(type)) {
                isDisabled = Boolean.TRUE.equals(configManager.getBoolean("disable_source_video"));
            } else if (Integer.valueOf(2).equals(type)) {
                isDisabled = Boolean.TRUE.equals(configManager.getBoolean("disable_source_image"));
            }

            if (!isDisabled) {
                String key = type + "-" + sourceFaceId; // goodsType=type, goodsId=faceId(源素材用faceId作为ID)
                if (!goodsMap.containsKey(key)) {
                    GoodsPageVO goodsPageVO = new GoodsPageVO();
                    goodsPageVO.setFaceId(sourceFaceId);
                    goodsPageVO.setGoodsType(type);
                    if (type == 1) {
                        goodsPageVO.setGoodsName("录像集");
                        goodsPageVO.setTemplateCoverUrl(configManager.getString("video_cover_url"));
                    } else if (type == 2) {
                        goodsPageVO.setGoodsName("照片集");
                        goodsPageVO.setTemplateCoverUrl(configManager.getString("photo_cover_url"));
                    } else if (type == 3) {
                        goodsPageVO.setGoodsName("AI微单");
                        goodsPageVO.setTemplateCoverUrl(configManager.getString("ai_camera_cover_url"));
                    } else {
                        goodsPageVO.setGoodsName("未知商品");
                    }
                    if (StringUtils.isBlank(goodsPageVO.getTemplateCoverUrl())) {
                        goodsPageVO.setTemplateCoverUrl(source.getUrl());
                    }
                    goodsPageVO.setScenicId(scenicId);
                    goodsMap.put(key, goodsPageVO);
                }
            }
        }

        // 返回去重后的商品列表
        List<GoodsPageVO> resultList = new ArrayList<>(goodsMap.values());
        return ApiResponse.success(resultList);
    }

}
