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

import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.digest.MD5;
import com.ycwl.basic.biz.FaceStatusManager;
import com.ycwl.basic.enums.FaceCutStatus;
import com.ycwl.basic.enums.TemplateRenderStatus;
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
import com.ycwl.basic.integration.common.manager.RenderWorkerConfigManager;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.integration.message.service.ZtMessageProducerService;
import com.ycwl.basic.integration.render.dto.job.CreatePreviewRequest;
import com.ycwl.basic.integration.render.dto.job.CreatePreviewResponse;
import com.ycwl.basic.integration.render.dto.job.MaterialDTO;
import com.ycwl.basic.integration.render.service.RenderJobIntegrationService;
import com.ycwl.basic.mapper.task.TaskRenderJobMappingMapper;
import com.ycwl.basic.model.task.entity.TaskRenderJobMappingEntity;
import com.ycwl.basic.repository.MemberRelationRepository;
import com.ycwl.basic.repository.SourceRepository;
import com.ycwl.basic.utils.JacksonUtil;
import com.ycwl.basic.biz.OrderBiz;
import com.ycwl.basic.biz.TemplateBiz;
import com.ycwl.basic.constant.StorageConstant;
import com.ycwl.basic.mapper.MemberMapper;
import com.ycwl.basic.mapper.TaskMapper;
import com.ycwl.basic.mapper.VideoMapper;
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.pc.member.resp.MemberRespVO;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import com.ycwl.basic.model.pc.renderWorker.entity.RenderWorkerEntity;
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.pc.task.entity.TaskEntity;
import com.ycwl.basic.model.pc.task.req.TaskReqQuery;
import com.ycwl.basic.model.pc.task.resp.TaskRespVO;
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.notify.req.WechatSubscribeNotifyTriggerRequest;
import com.ycwl.basic.model.pc.notify.resp.WechatSubscribeNotifyTriggerResult;
import com.ycwl.basic.model.task.req.ClientStatusReqVo;
import com.ycwl.basic.model.task.req.TaskReqVo;
import com.ycwl.basic.model.task.req.TaskSuccessReqVo;
import com.ycwl.basic.model.task.req.WorkerAuthReqVo;
import com.ycwl.basic.model.task.resp.TaskSyncRespVo;
import com.ycwl.basic.repository.DeviceRepository;
import com.ycwl.basic.repository.FaceRepository;
import com.ycwl.basic.repository.RenderWorkerRepository;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.repository.VideoRepository;
import com.ycwl.basic.repository.VideoTaskRepository;
import com.ycwl.basic.service.pc.ScenicService;
import com.ycwl.basic.service.notify.WechatSubscribeNotifyTriggerService;
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.task.VideoPieceGetter;
import com.ycwl.basic.repository.TemplateRepository;
import com.ycwl.basic.utils.SnowFlakeUtil;
import com.ycwl.basic.utils.NotificationAuthUtils;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

@Slf4j
@Service
public class TaskTaskServiceImpl implements TaskService {
    private static final String WORKER_SELF_HOSTED_CACHE_KEY = "worker_self_hosted_scenic:%s";
    private static final int CACHE_EXPIRE_MINUTES = 3;
    @Autowired
    private TaskMapper taskMapper;
    @Autowired
    private RenderWorkerRepository repository;
    @Autowired
    private VideoMapper videoMapper;
    @Autowired
    private TemplateRepository templateRepository;
    @Autowired
    private VideoTaskRepository videoTaskRepository;
    @Autowired
    private OrderBiz orderBiz;
    @Autowired
    private MemberMapper memberMapper;
    @Autowired
    private ScenicRepository scenicRepository;
    @Autowired
    private TemplateBiz templateBiz;
    @Autowired
    private FaceRepository faceRepository;
    @Autowired
    private VideoRepository videoRepository;
    @Autowired
    private ScenicService scenicService;

    private final ReentrantLock lock = new ReentrantLock();
    @Autowired
    private DeviceRepository deviceRepository;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private SourceRepository sourceRepository;
    @Autowired
    private MemberRelationRepository memberRelationRepository;
    @Autowired
    private WechatSubscribeNotifyTriggerService notifyTriggerService;
    @Autowired
    private FaceStatusManager faceStatusManager;
    @Autowired
    private RenderJobIntegrationService renderJobIntegrationService;
    @Autowired
    private TaskRenderJobMappingMapper taskRenderJobMappingMapper;

    private RenderWorkerEntity getWorker(@NonNull WorkerAuthReqVo req) {
        String accessKey = req.getAccessKey();
        if (accessKey == null) {
            return null;
        }
        RenderWorkerEntity worker = repository.getWorkerByAccessKey(accessKey);
        if (worker == null) {
            return null;
        }
        if (worker.getStatus() != 1) {
            return null;
        }
        return worker;
    }

    /**
     * 比较两个版本号
     * @param v1 版本号1
     * @param v2 版本号2
     * @return 负数表示 v1 < v2，0 表示相等，正数表示 v1 > v2
     */
    private int compareVersion(String v1, String v2) {
        String[] parts1 = v1.split("\\.");
        String[] parts2 = v2.split("\\.");
        int maxLen = Math.max(parts1.length, parts2.length);
        for (int i = 0; i < maxLen; i++) {
            int num1 = i < parts1.length ? parseVersionPart(parts1[i]) : 0;
            int num2 = i < parts2.length ? parseVersionPart(parts2[i]) : 0;
            if (num1 != num2) {
                return num1 - num2;
            }
        }
        return 0;
    }

    private int parseVersionPart(String part) {
        try {
            return Integer.parseInt(part.replaceAll("[^0-9]", ""));
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    private boolean isWorkerSelfHostedScenic(Long scenicId) {
        String cacheKey = String.format(WORKER_SELF_HOSTED_CACHE_KEY, scenicId);
        String cachedValue = redisTemplate.opsForValue().get(cacheKey);

        if (cachedValue != null) {
            return Boolean.parseBoolean(cachedValue);
        }

        // 缓存中没有，查询配置
        ScenicConfigManager config = scenicRepository.getScenicConfigManager(scenicId);
        boolean workerSelfHostedScenic = Boolean.TRUE.equals(config.getBoolean("worker_self_hosted"));

        // 缓存结果，设置30分钟过期
        redisTemplate.opsForValue().set(cacheKey, String.valueOf(workerSelfHostedScenic), CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);

        return workerSelfHostedScenic;
    }

    @Override
    public TaskSyncRespVo handleSyncTask(@NonNull TaskReqVo req) {
        RenderWorkerEntity worker = getWorker(req);
        if (worker == null) {
            return null;
        }
        worker.setName(null);
        worker.setStatus(null);
        // get status
        ClientStatusReqVo clientStatus = req.getClientStatus();
        // 版本校验：上报版本低于缓存版本时认为 worker 异常
        ClientStatusReqVo cachedStatus = repository.getWorkerHostStatus(worker.getId());
        if (cachedStatus != null && clientStatus != null
                && cachedStatus.getVersion() != null && clientStatus.getVersion() != null
                && compareVersion(clientStatus.getVersion(), cachedStatus.getVersion()) < 0) {
            return null;
        }
        repository.setWorkerHostStatus(worker.getId(), clientStatus);
        TaskSyncRespVo resp = new TaskSyncRespVo();
        // Template
        List<TemplateRespVO> updTemplateList;
        if (req.getTemplateList() != null) {
            updTemplateList = new ArrayList<>();
            // 上报了本地模板列表
            req.getTemplateList().forEach(template -> {
                if (StringUtils.isNumeric(template.getId())) {
                    TemplateRespVO dbTemplate = templateRepository.getTemplate(Long.parseLong(template.getId()));
                    if (dbTemplate == null) {
                        return;
                    }
                    if (!dbTemplate.getUpdateTime().equals(template.getUpdateTime())) {
                        updTemplateList.add(dbTemplate);
                    }
                }
            });
        } else {
            updTemplateList = templateRepository.getAllEnabledTemplateList();
        }
        try {
            if (lock.tryLock(2, TimeUnit.SECONDS)) {
                try {
                    resp.setTasks(Collections.emptyList());
                    resp.setTemplates(updTemplateList);
                } finally {
                    lock.unlock();
                }
            }
        } catch (InterruptedException ignored) {
        }
        // return Task
        return resp;
    }

    @Override
    public TemplateRespVO workerGetTemplate(@NonNull Long templateId, @NonNull WorkerAuthReqVo req) {
        RenderWorkerEntity worker = getWorker(req);
        if (worker == null) {
            return null;
        }
        return templateRepository.getTemplate(templateId);
    }

    @Override
    public void taskStart(@NonNull Long taskId, @NonNull WorkerAuthReqVo req) {
        TaskRespVO task = taskMapper.getById(taskId);
        if (task == null) {
            return;
        }
        RenderWorkerEntity worker = getWorker(req);
        if (worker == null) {
            return;
        }
        TaskEntity taskUpdate = new TaskEntity();
        taskUpdate.setId(taskId);
        taskUpdate.setWorkerId(worker.getId());
        taskMapper.setStart(taskUpdate);
        videoTaskRepository.clearTaskCache(taskUpdate.getId());
    }

    @Override
    public void forceCreateTaskByFaceIdAndTempalteId(Long faceId, Long templateId) {
        createTaskByFaceIdAndTemplateIdInternal(faceId, templateId, false, true);
    }

    @Override
    public void autoCreateTaskByFaceId(Long faceId) {
        FaceEntity faceRespVO = faceRepository.getFace(faceId);
        if (faceRespVO == null) {
            log.info("faceId:{} is not exist", faceId);
            return;
        }
        if (!StringUtils.isNotBlank(faceRespVO.getMatchSampleIds())) {
            log.info("faceId:{} matchSampleIds is empty", faceId);
            return;
        }
        List<FaceSampleEntity> faceSampleList = faceRepository.getFaceSampleList(faceId);
        if (faceSampleList.isEmpty()) {
            log.info("faceId:{} faceSampleList is empty", faceId);
            return;
        }
        List<Long> faceSampleIds = faceSampleList.stream()
                .sorted(Comparator.comparing(FaceSampleEntity::getCreateAt).reversed())
                .collect(Collectors.groupingBy(FaceSampleEntity::getDeviceId)).entrySet()
                .stream().flatMap(entry -> {
                    DeviceConfigManager configManager = deviceRepository.getDeviceConfigManager(entry.getKey());
                    if (configManager.getInteger("limit_video", 0) > 0) {
                        return entry.getValue().subList(0, Math.min(entry.getValue().size(), configManager.getInteger("limit_video", 0))).stream();
                    }
                    return entry.getValue().stream();
                }).toList()
                .stream().map(FaceSampleEntity::getId).toList();
        ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(faceRespVO.getScenicId());
        List<TemplateRespVO> templateList = templateRepository.getTemplateListByScenicId(faceRespVO.getScenicId());
        if (templateList == null || templateList.isEmpty()) {
            // 没有vlog视频的情况下
            VideoPieceGetter.Task task = new VideoPieceGetter.Task();
            task.faceId = faceId;
            task.faceSampleIds = faceSampleIds;
            task.templateId = null;
            task.memberId = faceRespVO.getMemberId();
            task.callback = () -> {
            };
            VideoPieceGetter.addTask(task);
            return;
        }
        if (Integer.valueOf(3).equals(scenicConfig.getInteger("book_routine")) || Integer.valueOf(4).equals(scenicConfig.getInteger("book_routine"))) {
            // 生成全部视频的逻辑
            templateList.forEach(template -> createTaskByFaceIdAndTemplateId(faceId, template.getId(), true));
        } else {
            if (Boolean.TRUE.equals(scenicConfig.getBoolean("force_create_vlog"))) {
                Long availableTemplateId = templateBiz.findFirstAvailableTemplate(templateList.stream().map(TemplateRespVO::getId).toList(), faceId, false);
                if (availableTemplateId != null) {
                    createTaskByFaceIdAndTemplateId(faceId, availableTemplateId, true);
                } else {
                    log.info("faceId:{} available template is not exist", faceId);
                }
            } else {
                // 非强制创建，只创建第一个可用模板
                if (!templateList.isEmpty()) {
                    createTaskByFaceIdAndTemplateId(faceId, templateList.getFirst().getId(), true);
                }
            }
        }
    }

    @Override
    public void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId, boolean automatic) {
        createTaskByFaceIdAndTemplateIdInternal(faceId, templateId, automatic, false);
    }

    private void createTaskByFaceIdAndTemplateIdInternal(Long faceId, Long templateId, boolean automatic, boolean forceCreate) {
        FaceEntity face = faceRepository.getFace(faceId);
        if (face == null) {
            log.info("faceId:{} is not exist", faceId);
            return;
        }
        List<FaceSampleEntity> faceSampleList = faceRepository.getFaceSampleList(faceId);
        if (faceSampleList.isEmpty()) {
            log.info("faceId:{} sample list not exist", faceId);
        }
        
        // 只有在非强制创建时才进行模板生成判断和状态设置
        if (!forceCreate) {
            if (templateBiz.determineTemplateCanGenerate(templateId, faceId, false)) {
                // 临时写死，当自动生成视频，切片也算合成中，并更新状态
                faceStatusManager.setFaceCutStatus(face.getId(), FaceCutStatus.CUTTING);
            } else {
                faceStatusManager.setFaceCutStatus(face.getId(), FaceCutStatus.WAITING_USER_SELECT);
            }
        }
        
        List<Long> faceSampleIds = faceSampleList.stream()
                .sorted(Comparator.comparing(FaceSampleEntity::getCreateAt).reversed())
                .collect(Collectors.groupingBy(FaceSampleEntity::getDeviceId)).entrySet()
                .stream().flatMap(entry -> {
                    DeviceConfigManager configManager = deviceRepository.getDeviceConfigManager(entry.getKey());
                    if (configManager.getInteger("limit_video", 0) > 0) {
                        return entry.getValue().subList(0, Math.min(entry.getValue().size(), configManager.getInteger("limit_video", 0))).stream();
                    }
                    return entry.getValue().stream();
                }).toList()
                .stream().map(FaceSampleEntity::getId).collect(Collectors.toList());
        VideoPieceGetter.Task task = new VideoPieceGetter.Task();
        task.faceId = faceId;
        task.faceSampleIds = faceSampleIds;
        task.templateId = templateId;
        task.memberId = face.getMemberId();
        task.callback = () -> {
            faceStatusManager.setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.RENDERING);
            // 只有在非强制创建时才进行模板生成判断
            if (!forceCreate) {
                boolean canGenerate = templateBiz.determineTemplateCanGenerate(templateId, faceId);
                if (!canGenerate) {
                    faceStatusManager.setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.NONE);
                    log.info("task callback: 不能生成，templateId: {}", templateId);
                    return;
                }
            }
            
            Map<String, List<SourceEntity>> allTaskParams = sourceRepository.getTaskParams(faceId, templateId);
            if (allTaskParams.isEmpty()) {
                faceStatusManager.setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.NONE);
                log.info("task callback: 任务参数没有，templateId: {}", templateId);
                return;
            }
            
            Map<String, List<SourceEntity>> sourcesMap = templateBiz.filterTaskParams(templateId, allTaskParams);
            if (sourcesMap.isEmpty()) {
                faceStatusManager.setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.NONE);
                log.info("task callback: 筛选后无有效源数据，templateId: {}", templateId);
                return;
            }
            TaskReqQuery taskReqQuery = new TaskReqQuery();
            taskReqQuery.setFaceId(faceId);
            taskReqQuery.setTemplateId(templateId);
            taskReqQuery.setTaskParams(JacksonUtil.toJSONString(sourcesMap));
            List<TaskRespVO> list = taskMapper.list(taskReqQuery);

            MemberVideoEntity memberVideoEntity = new MemberVideoEntity();
            memberVideoEntity.setMemberId(face.getMemberId());
            memberVideoEntity.setScenicId(face.getScenicId());
            memberVideoEntity.setFaceId(faceId);
            memberVideoEntity.setTemplateId(templateId);
            memberVideoEntity.setIsBuy(0);
            if (list.isEmpty()) {
                log.info("创建任务! faceId:{},templateId:{}", faceId, templateId);
                ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
                TaskEntity taskEntity = null;
                boolean isReuseOldTask = false;
                if (Integer.valueOf(0).equals(scenicConfig.getInteger("template_new_video_type"))) {
                    log.info("景区{}启用：templateNewVideoType：全新视频原位替换", face.getScenicId());
                    taskReqQuery.setTemplateId(templateId);
                    taskReqQuery.setTaskParams(null);  // 原位替换模式下，不按taskParams匹配
                    List<TaskEntity> templateTaskList = taskMapper.listEntity(taskReqQuery);
                    if (!templateTaskList.isEmpty()) {
                        taskEntity = templateTaskList.getFirst();
                        isReuseOldTask = true;
                        if (automatic && !forceCreate) {
                            boolean autoReplaceVlog = scenicConfig.getBoolean("auto_replace_vlog", true);

                            if (!autoReplaceVlog) {
                                VideoEntity video = videoRepository.getVideoByTaskId(taskEntity.getId());
                                if (video != null) {
                                    log.info("自动创建任务：跳过（auto_replace_vlog=false）, faceId:{}, templateId:{}, existingTaskId:{}, videoId:{}",
                                            faceId, templateId, taskEntity.getId(), video.getId());
                                    faceStatusManager.setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.RENDERED);
                                    return;
                                }
                            }
                        }
                        log.info("已有旧生成的视频：{}", taskEntity);
                        MemberVideoEntity taskVideoRelation = videoMapper.queryRelationByMemberTask(face.getMemberId(), taskEntity.getId());
                        if (taskVideoRelation != null) {
                            log.info("已有旧关联记录的视频：{}", taskVideoRelation);
                            memberVideoEntity.setIsBuy(taskVideoRelation.getIsBuy());
                            memberVideoEntity.setOrderId(taskVideoRelation.getOrderId());
                        }
                    }
                }
                if (taskEntity == null) {
                    taskEntity = new TaskEntity();
                    taskEntity.setId(SnowFlakeUtil.getLongId());
                    taskEntity.setScenicId(face.getScenicId());
                    taskEntity.setFaceId(faceId);
                    taskEntity.setTemplateId(templateId);
                    taskEntity.setAutomatic(automatic ? 1 : 0);
                }
                taskEntity.setStatus(0);
                taskEntity.setTaskParams(JacksonUtil.toJSONString(sourcesMap));
                if (isReuseOldTask) {
                    taskMapper.update(taskEntity);
                    taskMapper.deassign(taskEntity.getId());
                    log.info("更新旧任务! taskId:{}", taskEntity.getId());
                } else {
                    taskMapper.add(taskEntity);
                }
                // 灰度测试：创建渲染预览任务（异步，不影响主流程）
                tryCreateRenderPreviewJobAsync(taskEntity.getId(), templateId, face.getScenicId(), faceId, face.getMemberId(), sourcesMap);
                memberVideoEntity.setTaskId(taskEntity.getId());
            } else {
                TaskRespVO existingTask = list.getFirst();


                log.info("重复task! faceId:{},templateId:{},taskId:{}", faceId, templateId, existingTask.getId());
                videoTaskRepository.clearTaskCache(existingTask.getId());

                memberVideoEntity.setTaskId(existingTask.getId());
                VideoEntity video = videoRepository.getVideoByTaskId(existingTask.getId());
                if (video != null) {
                    IsBuyRespVO isBuy = orderBiz.isBuy(existingTask.getScenicId(), face.getMemberId(), face.getId(), 0, video.getId());
                    if (isBuy.isBuy()) {
                        memberVideoEntity.setIsBuy(1);
                        memberVideoEntity.setOrderId(isBuy.getOrderId());
                    }
                    if (isBuy.isFree()) {
                        memberVideoEntity.setIsBuy(1);
                    }
                    memberVideoEntity.setVideoId(video.getId());
                }
            }
            videoMapper.addRelation(memberVideoEntity);
            memberRelationRepository.clearVCacheByFace(faceId);
            // 仅当复用已有视频时立即标记为已渲染，新任务由 RenderJobPollingService 在 PREVIEW_READY 时更新
            if (memberVideoEntity.getVideoId() != null) {
                faceStatusManager.setTemplateRenderStatus(faceId, templateId, TemplateRenderStatus.RENDERED);
            }
            faceStatusManager.markNoNewPieces(faceId, templateId);
        };
        VideoPieceGetter.addTask(task);
    }

    @Override
    public void taskSuccess(@NonNull Long taskId, @NonNull TaskSuccessReqVo req) {
        TaskRespVO task = taskMapper.getById(taskId);
        if (task == null) {
            return;
        }
        RenderWorkerEntity worker = getWorker(req);
        if (worker == null) {
            return;
        }
        TaskEntity taskUpdate = new TaskEntity();
        taskUpdate.setId(taskId);
        taskUpdate.setStatus(1);
        taskUpdate.setWorkerId(worker.getId());
        taskMapper.setSuccess(taskUpdate);
        videoTaskRepository.clearTaskCache(taskUpdate.getId());

        VideoEntity video = videoMapper.findByTaskId(taskId);
        if (video != null) {
            video.setVideoUrl(task.getVideoUrl());
            if (req.getVideoInfo() != null) {
                video.setHeight(req.getVideoInfo().getHeight());
                video.setWidth(req.getVideoInfo().getWidth());
                video.setDuration(req.getVideoInfo().getDuration());
            }
            videoMapper.update(video);
            videoRepository.clearVideoCache(video.getId());
        } else {
            video = new VideoEntity();
            video.setId(SnowFlakeUtil.getLongId());
            video.setScenicId(task.getScenicId());
            video.setTemplateId(task.getTemplateId());
            video.setTaskId(taskId);
            video.setFaceId(task.getFaceId());
            video.setVideoUrl(task.getVideoUrl());
            video.setCreateTime(new Date());
            if (req.getVideoInfo() != null) {
                video.setHeight(req.getVideoInfo().getHeight());
                video.setWidth(req.getVideoInfo().getWidth());
                video.setDuration(req.getVideoInfo().getDuration());
            }
            videoMapper.add(video);
        }
        ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(task.getScenicId());
        int isBuy = 0;
        FaceEntity face = faceRepository.getFace(task.getFaceId());
        if (face != null) {
            IsBuyRespVO priceObj = orderBiz.isBuy(task.getScenicId(), face.getMemberId(), face.getId(), 0, video.getId());
            if (priceObj.isBuy()) {
                isBuy = 1;
            }
            if (priceObj.isFree()) {
                isBuy = 1;
            }
            if (isBuy != 1) {
                // 判断景区新生成免费送逻辑
                if (scenicConfig != null && Integer.valueOf(2).equals(scenicConfig.getInteger("template_new_video_type"))) {
                    log.info("景区{}启用：templateNewVideoType：全新视频不需要重新购买", task.getScenicId());
                    // 全新视频，不需要重新购买，旧视频在我的里面查看
                    List<MemberVideoEntity> entityList = videoMapper.listRelationByFaceAndTemplate(face.getId(), task.getTemplateId());
                    Optional<MemberVideoEntity> buy = entityList.stream().filter(item -> Integer.valueOf(1).equals(item.getIsBuy())).findAny();
                    if (buy.isPresent()) {
                        log.info("人脸{}，模板{}，已经被购买过，{}", face.getId(), task.getTemplateId(), buy.get());
                        // 买过
                        isBuy = 1;
                    }
                }
            }
        }
        videoMapper.updateRelationWhenTaskSuccess(taskId, video.getId(), isBuy);
        memberRelationRepository.clearVCacheByFace(task.getFaceId());
        Thread.ofVirtual().start(() -> sendVideoGeneratedServiceNotification(taskId));
    }

    @Override
    public void taskFail(Long taskId, WorkerAuthReqVo req) {
        TaskRespVO task = taskMapper.getById(taskId);
        if (task == null) {
            return;
        }
        RenderWorkerEntity worker = getWorker(req);
        if (worker == null) {
            return;
        }
        TaskEntity taskUpdate = new TaskEntity();
        taskUpdate.setId(taskId);
        taskUpdate.setStatus(2);
        taskUpdate.setWorkerId(worker.getId());
        taskMapper.setFail(taskUpdate);
        taskMapper.deassign(taskId);
        videoTaskRepository.clearTaskCache(taskUpdate.getId());
    }

    @Override
    public String getUploadUrl(Long taskId, WorkerAuthReqVo req) {
        TaskRespVO task = taskMapper.getById(taskId);
        if (task == null) {
            return null;
        }
        RenderWorkerEntity worker = getWorker(req);
        if (worker == null) {
            return null;
        }
        RenderWorkerConfigManager config = repository.getWorkerConfigManager(worker.getId());
        IStorageAdapter adapter;
        try {
            adapter = StorageFactory.get(config.getString("store_type"));
            adapter.loadConfig(config.getObject("store_config_json", Map.class));
        } catch (Exception e) {
            adapter = scenicService.getScenicStorageAdapter(task.getScenicId());
        }
        String hash = MD5.create().digestHex(task.getTaskParams() + task.getFaceId().toString());
        String filename = StorageUtil.joinPath(StorageConstant.VLOG_PATH, task.getTemplateId().toString() + "_" + hash + "_" + task.getScenicId() + ".mp4");
        // 生成
        String url = adapter.getUrl(filename);
        TaskEntity updateTask = new TaskEntity();
        updateTask.setId(taskId);
        updateTask.setVideoUrl(url);
        taskMapper.update(updateTask);
        videoTaskRepository.clearTaskCache(updateTask.getId());
        return adapter.getUrlForUpload(new Date(System.currentTimeMillis() + 1000 * 3600), "video/mp4", filename);
    }

    public void sendVideoGeneratedServiceNotification(Long taskId) {
        List<MemberVideoEntity> memberVideo = videoMapper.listRelationByTask(taskId);
        memberVideo.forEach(item -> sendVideoGeneratedServiceNotification(taskId, item.getMemberId()));
    }

    @Override
    public void sendVideoGeneratedServiceNotification(Long taskId, Long memberId) {
        MemberVideoEntity item = videoMapper.queryRelationByMemberTask(memberId, taskId);
        MemberRespVO member = memberMapper.getById(memberId);
        if (member == null || item == null) {
            log.warn("sendVideoGeneratedServiceNotification member or item is null, memberId:{}, taskId:{}", memberId, taskId);
            return;
        }
        String openId = member.getOpenId();
        ScenicV2DTO scenic = scenicRepository.getScenicBasic(item.getScenicId());
        MpConfigEntity scenicMp = scenicRepository.getScenicMpConfig(member.getScenicId());
        if (StringUtils.isNotBlank(openId) && scenicMp != null) {
            // 发送模板消息
            Map<String, Object> variables = new HashMap<>();
            variables.put("taskId", taskId);
            variables.put("scenicId", scenic.getId());
            variables.put("scenicName", scenic.getName());
            variables.put("faceId", item.getFaceId());
            variables.put("videoId", item.getVideoId());
            ScenicConfigManager configManager = scenicRepository.getScenicConfigManager(item.getScenicId());
            if (configManager.getBoolean("grouping_enable", false)) {
                variables.put("videoResultPage", "travelVideoCenter");
            } else {
                variables.put("videoResultPage", "videoSynthesis");
            }
            variables.put("videoDeviceCount", videoTaskRepository.getTaskDeviceNum(taskId));
            variables.put("videoLensCount", videoTaskRepository.getTaskLensNum(taskId));
            variables.put("videoShotTime", DateUtil.format(videoTaskRepository.getTaskShotDate(taskId), "yyyy-MM-dd HH:mm"));
            variables.put("videoCreateTime", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm"));

            WechatSubscribeNotifyTriggerRequest request = WechatSubscribeNotifyTriggerRequest.builder()
                    .scenicId(item.getScenicId())
                    .memberId(item.getMemberId())
                    .openId(member.getOpenId())
                    .bizId(String.valueOf(item.getId()))
                    .variables(variables)
                    .build();
            WechatSubscribeNotifyTriggerResult triggerResult = notifyTriggerService.trigger("VIDEO_GENERATED", request);
        }
    }

    @Override
    public TaskRespVO taskInfo(Long taskId) {
        return taskMapper.getById(taskId);
    }

    /**
     * 灰度测试：异步创建渲染预览任务
     * 不管zt-render-worker服务返回什么或者报错，都不影响现有流程
     */
    private void tryCreateRenderPreviewJobAsync(Long taskId, Long templateId, Long scenicId, Long faceId, Long memberId, Map<String, List<SourceEntity>> sourcesMap) {
        Thread.ofVirtual().start(() -> {
            try {
                log.info("[灰度测试] 开始创建渲染预览任务, taskId: {}, templateId: {}, scenicId: {}, faceId: {}",
                        taskId, templateId, scenicId, faceId);

                CreatePreviewRequest request = new CreatePreviewRequest();
                request.setTemplateId(templateId);
                request.setScenicId(scenicId);
                request.setFaceId(faceId);
                request.setMemberId(memberId);

                // 转换素材映射
                Map<String, List<MaterialDTO>> materialsBySlot = new java.util.HashMap<>();
                if (sourcesMap != null && !sourcesMap.isEmpty()) {
                    sourcesMap.forEach((slotKey, sources) -> {
                        List<MaterialDTO> materials = sources.stream()
                                .map(source -> {
                                    MaterialDTO material = new MaterialDTO();
                                    // 优先使用videoUrl，其次使用url
                                    if (StringUtils.isNotBlank(source.getVideoUrl())) {
                                        material.setUrl(source.getVideoUrl());
                                        material.setType("video");
                                    } else {
                                        material.setUrl(source.getUrl());
                                        material.setType(source.getType() != null && source.getType() == 2 ? "image" : "video");
                                    }
                                    material.setFacePos(source.getPosJson());
                                    return material;
                                })
                                .toList();
                        materialsBySlot.put(slotKey, materials);
                    });
                }
                request.setMaterialsBySlot(materialsBySlot);

                CreatePreviewResponse response = renderJobIntegrationService.createPreview(request);
                log.info("[灰度测试] 渲染预览任务创建成功, taskId: {}, renderJobId: {}, playUrl: {}",
                        taskId, response.getJobId(), response.getPlayUrl());

                // 写入mapping表，供轮询服务处理
                try {
                    // 原位替换模式下可能已有旧映射，先删除再插入
                    TaskRenderJobMappingEntity existingMapping = taskRenderJobMappingMapper.selectByTaskId(taskId);
                    if (existingMapping != null) {
                        taskRenderJobMappingMapper.deleteById(existingMapping.getId());
                        log.info("[灰度测试] 已删除旧mapping, taskId: {}, oldRenderJobId: {}", taskId, existingMapping.getRenderJobId());
                    }
                    TaskRenderJobMappingEntity mapping = new TaskRenderJobMappingEntity();
                    mapping.setTaskId(taskId);
                    mapping.setRenderJobId(response.getJobId());
                    mapping.setRenderStatus(TaskRenderJobMappingEntity.STATUS_PENDING);
                    mapping.setPublishedCount(0);
                    mapping.setSegmentCount(0);
                    mapping.setRetryCount(0);
                    taskRenderJobMappingMapper.insert(mapping);
                    log.info("[灰度测试] 写入mapping成功, taskId: {}, renderJobId: {}", taskId, response.getJobId());
                } catch (Exception ex) {
                    log.warn("[灰度测试] 写入mapping失败，不影响主流程, taskId: {}, error: {}", taskId, ex.getMessage());
                }
            } catch (Exception e) {
                // 灰度测试：不管返回什么或者报错，都不影响现有流程
                log.warn("[灰度测试] 渲染预览任务创建失败，不影响主流程, taskId: {}, templateId: {}, error: {}",
                        taskId, templateId, e.getMessage());
            }
        });
    }
}
