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

import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.digest.MD5;
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.dto.ZtMessage;
import com.ycwl.basic.integration.message.service.ZtMessageProducerService;
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.TaskStatusBiz;
import com.ycwl.basic.biz.TemplateBiz;
import com.ycwl.basic.constant.StorageConstant;
import com.ycwl.basic.mapper.FaceMapper;
import com.ycwl.basic.mapper.FaceSampleMapper;
import com.ycwl.basic.mapper.MemberMapper;
import com.ycwl.basic.mapper.SourceMapper;
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.face.resp.FaceRespVO;
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.model.pc.scenic.entity.ScenicConfigEntity;
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.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.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.VideoReUploader;
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.Arrays;
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 String VIDEO_NOTIFICATION_CACHE_KEY = "video_notification_member:%s";
    private static final int CACHE_EXPIRE_MINUTES = 3;
    private static final int NOTIFICATION_CACHE_EXPIRE_MINUTES = 2;
    @Autowired
    private TaskMapper taskMapper;
    @Autowired
    private RenderWorkerRepository repository;
    @Autowired
    private FaceMapper faceMapper;
    @Autowired
    private FaceSampleMapper faceSampleMapper;
    @Autowired
    private SourceMapper sourceMapper;
    @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 TaskStatusBiz taskStatusBiz;
    @Autowired
    private DeviceRepository deviceRepository;
    @Autowired
    private VideoReUploader videoReUploader;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private SourceRepository sourceRepository;
    @Autowired
    private MemberRelationRepository memberRelationRepository;
    @Autowired
    private ZtMessageProducerService ztMessageProducerService;
    @Autowired
    private NotificationAuthUtils notificationAuthUtils;

    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;
    }

    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();
        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();
        }
        RenderWorkerConfigManager configManager = repository.getWorkerConfigManager(worker.getId());
        try {
            if (lock.tryLock(2, TimeUnit.SECONDS)) {
                try {
                    List<TaskRespVO> taskList;
                    if (Strings.isNotBlank(configManager.getString("scenic_only"))) {
                        taskList = taskMapper.selectNotRunningByScenicList(configManager.getString("scenic_only"));
                    } else {
                        var _taskList = taskMapper.selectNotRunning();
                        taskList = _taskList.stream().filter(task -> {
                            boolean workerSelfHostedScenic = isWorkerSelfHostedScenic(task.getScenicId());
                            return !workerSelfHostedScenic;
                        }).limit(1).toList();
                    }
                    resp.setTasks(taskList);
                    resp.setTemplates(updTemplateList);
                    taskList.forEach(task -> {
                        taskMapper.assignToWorker(task.getId(), worker.getId());
                        videoTaskRepository.clearTaskCache(task.getId());
                    });
                } 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, 0, 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();
        log.info("视频切分任务: faceId={}, 原始数量={}, 筛选后数量={}", faceId, faceSampleList.size(), faceSampleIds.size());
        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 = () -> {
                log.info("task callback: {}", task);
            };
            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(), 1));
        } 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, 1);
                } else {
                    log.info("faceId:{} available template is not exist", faceId);
                }
            } else {
                // 非强制创建，只创建第一个可用模板
                if (!templateList.isEmpty()) {
                    createTaskByFaceIdAndTemplateId(faceId, templateList.getFirst().getId(), 1);
                }
            }
        }
    }
    @Override
    public void createTaskByFaceIdAndTemplateId(Long faceId, Long templateId) {
        createTaskByFaceIdAndTemplateId(faceId, templateId, 0);
    }

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

    private void createTaskByFaceIdAndTemplateIdInternal(Long faceId, Long templateId, int 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)) {
                // 临时写死，当自动生成视频，切片也算合成中，并更新状态
                taskStatusBiz.setFaceCutStatus(face.getId(), 0);
            } else {
                taskStatusBiz.setFaceCutStatus(face.getId(), 2);
            }
        }
        
        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());
        log.info("视频切分任务: faceId={}, 原始数量={}, 筛选后数量={}", faceId, faceSampleList.size(), faceSampleIds.size());
        List<SourceEntity> sourceList = sourceMapper.listVideoByScenicFaceRelation(face.getScenicId(), faceId);
        VideoPieceGetter.Task task = new VideoPieceGetter.Task();
        task.faceId = faceId;
        task.faceSampleIds = faceSampleIds;
        task.templateId = templateId;
        task.memberId = face.getMemberId();
        task.callback = () -> {
            log.info("task callback: {}", task);
            
            // 只有在非强制创建时才进行模板生成判断
            if (!forceCreate) {
                boolean canGenerate = templateBiz.determineTemplateCanGenerate(templateId, faceId);
                if (!canGenerate) {
                    log.info("task callback: 不能生成，templateId: {}", templateId);
                    return;
                }
            }
            
            Map<String, List<SourceEntity>> allTaskParams = sourceRepository.getTaskParams(faceId, templateId);
            if (allTaskParams.isEmpty()) {
                return;
            }
            
            Map<String, List<SourceEntity>> sourcesMap = templateBiz.filterTaskParams(templateId, allTaskParams);
            if (sourcesMap.isEmpty()) {
                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:{},taskParams:{}", faceId, templateId, sourcesMap);
                ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(face.getScenicId());
                TaskEntity taskEntity = null;
                if (Integer.valueOf(0).equals(scenicConfig.getTemplateNewVideoType())) {
                    log.info("景区{}启用：templateNewVideoType：全新视频原位替换", face.getScenicId());
                    taskReqQuery.setTemplateId(templateId);
                    List<TaskEntity> templateTaskList = taskMapper.listEntity(taskReqQuery);
                    if (!templateTaskList.isEmpty()) {
                        taskEntity = templateTaskList.getFirst();
                        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());
                        }
                        taskMapper.deleteById(taskEntity.getId());
                    }
                }
                if (taskEntity == null) {
                    taskEntity = new TaskEntity();
                    taskEntity.setId(SnowFlakeUtil.getLongId());
                    taskEntity.setScenicId(face.getScenicId());
                    taskEntity.setFaceId(faceId);
                    taskEntity.setTemplateId(templateId);
                    taskEntity.setAutomatic(automatic);
                }
                taskEntity.setWorkerId(null);
                taskEntity.setStatus(0);
                taskEntity.setTaskParams(JacksonUtil.toJSONString(sourcesMap));
                taskMapper.add(taskEntity);
                memberVideoEntity.setTaskId(taskEntity.getId());
            } else {
                log.info("重复task! faceId:{},templateId:{},taskParams:{}", faceId, templateId, sourcesMap);
                memberVideoEntity.setTaskId(list.getFirst().getId());
                VideoEntity video = videoMapper.findByTaskId(list.getFirst().getId());
                if (video != null) {
                    IsBuyRespVO isBuy = orderBiz.isBuy(face.getMemberId(), list.getFirst().getScenicId(), 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);
            
            // 只有在非强制创建时才更新切割任务状态
            if (!forceCreate) {
                // 任务生成了，需要更新切割任务状态
                taskStatusBiz.setFaceCutStatus(faceId, 2);
            }
        };
        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.setWorkerId(worker.getId());
            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);
        }
        ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(task.getScenicId());
        IStorageAdapter adapter = scenicService.getScenicTmpStorageAdapter(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");
        adapter.setAcl(StorageAcl.PUBLIC_READ, filename);
        int isBuy = 0;
        FaceEntity face = faceRepository.getFace(task.getFaceId());
        if (face != null) {
            IsBuyRespVO priceObj = orderBiz.isBuy(face.getMemberId(), task.getScenicId(), 0, video.getId());
            if (priceObj.isBuy()) {
                isBuy = 1;
            }
            if (priceObj.isFree()) {
                isBuy = 1;
            }
            if (isBuy != 1) {
                // 判断景区新生成免费送逻辑
                if (scenicConfig != null && Integer.valueOf(2).equals(scenicConfig.getTemplateNewVideoType())) {
                    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 Date getTaskShotDate(Long taskId) {
        TaskRespVO taskRespVO = taskMapper.getById(taskId);
        if (taskRespVO == null) {
            return null;
        }
        Date shotTime = taskRespVO.getCreateTime();
        Map<String, Object> paramJson = JacksonUtil.parseObject(taskRespVO.getTaskParams(), Map.class);
        if (paramJson != null) {
            Optional<String> any = paramJson.keySet().stream().filter(StringUtils::isNumeric).findAny();
            if (any.isPresent()) {
                @SuppressWarnings("unchecked")
                List<Map<String, Object>> jsonArray = (List<Map<String, Object>>) paramJson.get(any.get());
                if (jsonArray != null && !jsonArray.isEmpty()) {
                    Map<String, Object> jsonObject = jsonArray.get(0);
                    if (jsonObject.containsKey("createTime")) {
                        Object createTimeObj = jsonObject.get("createTime");
                        if (createTimeObj instanceof Number) {
                            shotTime = new Date(((Number) createTimeObj).longValue());
                        }
                    }
                }
            }
        }
        return shotTime;
    }

    @Override
    public void sendVideoGeneratedServiceNotification(Long taskId, Long memberId) {
        MemberVideoEntity item = videoMapper.queryRelationByMemberTask(memberId, taskId);
        MemberRespVO member = memberMapper.getById(memberId);
        String openId = member.getOpenId();
        MpConfigEntity scenicMp = scenicRepository.getScenicMpConfig(member.getScenicId());
        if (StringUtils.isNotBlank(openId) && scenicMp != null) {
            String templateId = scenicRepository.getVideoGeneratedTemplateId(item.getScenicId());
            if (StringUtils.isBlank(templateId)) {
                log.warn("未配置视频生成通知模板");
                return;
            }

            // 检查并消费通知授权
            if (!notificationAuthUtils.checkAndConsumeAuthorization(memberId, templateId, item.getScenicId())) {
                log.debug("用户[memberId={}]对模板[templateId={}]授权不足，跳过视频生成通知", memberId, templateId);
                return;
            }

            ScenicV2DTO scenic = scenicRepository.getScenicBasic(item.getScenicId());
            ScenicConfigManager configManager = scenicRepository.getScenicConfigManager(item.getScenicId());
            String configTitle = configManager.getString("first_notification_title");
            String configContent = configManager.getString("first_notification_content");

            if (StringUtils.isBlank(configTitle) || StringUtils.isBlank(configContent)) {
                log.info("景区[{}]未配置第一次通知内容，跳过发送通知", scenic.getName());
                return;
            }

            String title = configTitle.replace("【景区】", scenic.getName());
            String page;
            if (configManager.getBoolean("grouping_enable", false)) {
                page = "pages/travelVideoCenter/index?type=1&scenicId=" + item.getScenicId() + "&faceId=" + item.getFaceId();
            } else {
                page = "pages/videoSynthesis/index?type=1&scenicId=" + item.getScenicId() + "&faceId=" + item.getFaceId();
            }
            /**
             * 视频名称 {{thing1.DATA}}
             * 生成时间 {{time4.DATA}}
             * 备注 {{thing3.DATA}}
             */
            Map<String, Object> dataParam = new HashMap<>();
            dataParam.put("thing1", title);
            dataParam.put("time4", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm"));
            dataParam.put("thing3", configContent);

            // 构建extra，只包含data和page
            Map<String, Object> extra = new HashMap<>();
            extra.put("data", dataParam);
            extra.put("page", page);

            // 使用ZT消息服务发送通知（第一次通知）
            ZtMessage msg = new ZtMessage();
            msg.setChannelId(templateId);
            msg.setTitle(title);
            msg.setContent("【" + item.getFaceId() + "/" + item.getVideoId() + "】"+configContent);
            msg.setTarget(openId);
            msg.setExtra(extra);
            msg.setSendReason("视频生成通知");
            msg.setSendBiz("视频生成");
            ztMessageProducerService.send(msg);
            log.info("memberId:{} 视频生成通知发送成功", memberId);
        }
    }

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