package com.yaoai.pipeline.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yaoai.ai.providers.model.VideoTaskResult;
import com.yaoai.ai.providers.service.SeedanceService;
import com.yaoai.ai.providers.util.SeedancePromptBuilder;
import com.yaoai.billing.dto.BillingChargeRequest;
import com.yaoai.billing.service.BillingService;
import com.yaoai.common.exception.BizException;
import com.yaoai.common.exception.ErrorCode;
import com.yaoai.domain.entity.AiTask;
import com.yaoai.domain.entity.Storyboard;
import com.yaoai.domain.mapper.AiTaskMapper;
import com.yaoai.domain.mapper.StoryboardMapper;
import com.yaoai.pipeline.async.VideoTaskAsyncProcessor;
import com.yaoai.pipeline.dto.StructuredVideoSubmission;
import com.yaoai.pipeline.service.ShotAssetService;
import com.yaoai.pipeline.service.VideoTaskPipelineService;
import com.yaoai.storage.service.TosService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class VideoTaskPipelineServiceImpl implements VideoTaskPipelineService {

    private final SeedanceService seedanceService;
    private final TosService tosService;
    private final AiTaskMapper aiTaskMapper;
    private final StoryboardMapper storyboardMapper;
    private final BillingService billingService;
    private final VideoTaskAsyncProcessor asyncProcessor;
    private final ShotAssetService shotAssetService;

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Override
    public AiTask submitVideoTask(Long projectId, Long tenantId, Long episodeId,
                                  Long storyboardId, String imageKey, String prompt) {
        String presignedUrl = tosService.presignedGetUrl(imageKey, 3600);

        log.info("Submitting video task: projectId={}, episodeId={}, storyboardId={}", projectId, episodeId, storyboardId);
        billingService.checkBalance(BillingChargeRequest.builder()
                .tenantId(tenantId)
                .userId(StpUtil.getLoginIdAsLong())
                .projectId(projectId)
                .operation("video_generate")
                .modality("VIDEO")
                .modelProvider(seedanceService.getProvider())
                .modelId(seedanceService.getModelId())
                .modelName(seedanceService.getModelName())
                .unitCount(1)
                .build());

        String externalTaskId = seedanceService.submitVideoTask(presignedUrl, prompt);

        AiTask task = new AiTask();
        task.setTenantId(tenantId);
        task.setProjectId(projectId);
        task.setEpisodeId(episodeId);
        task.setStoryboardId(storyboardId);
        task.setTaskType("image_to_video");
        task.setExternalTaskId(externalTaskId);
        task.setStatus("submitted");
        task.setInputImageKey(imageKey);
        task.setPrompt(prompt);

        aiTaskMapper.insert(task);
        shotAssetService.recordPendingVideoAsset(
                tenantId,
                projectId,
                episodeId,
                storyboardId,
                task.getId(),
                prompt,
                seedanceService.getProvider(),
                seedanceService.getModelId(),
                List.of(imageKey),
                null,
                null,
                null,
                java.util.Map.of("legacy", true, "mode", "image_to_video")
        );
        billingService.charge(BillingChargeRequest.builder()
                .tenantId(tenantId)
                .userId(StpUtil.getLoginIdAsLong())
                .projectId(projectId)
                .operation("video_generate")
                .modality("VIDEO")
                .modelProvider(seedanceService.getProvider())
                .modelId(seedanceService.getModelId())
                .modelName(seedanceService.getModelName())
                .unitCount(1)
                .refId(String.valueOf(task.getId()))
                .build());
        log.info("AiTask created: id={}, externalId={}", task.getId(), externalTaskId);
        return task;
    }

    @Override
    public AiTask pollTaskStatus(Long taskId, Long tenantId) {
        AiTask task = aiTaskMapper.selectById(taskId);
        if (task == null || !tenantId.equals(task.getTenantId())) {
            throw new BizException(ErrorCode.NOT_FOUND, "任务不存在");
        }

        if ("succeeded".equals(task.getStatus()) || "failed".equals(task.getStatus())) {
            return task;
        }

        VideoTaskResult result = seedanceService.getTaskStatus(task.getExternalTaskId());

        task.setStatus(result.getStatus());
        if (result.getVideoUrl() != null) {
            task.setResultVideoUrl(result.getVideoUrl());
        }
        if (result.getErrorMessage() != null) {
            task.setErrorMessage(result.getErrorMessage());
        }

        aiTaskMapper.updateById(task);
        if ("succeeded".equals(task.getStatus())) {
            shotAssetService.markVideoAssetSucceeded(tenantId, taskId, task.getResultVideoUrl());
        } else if ("failed".equals(task.getStatus())) {
            shotAssetService.markVideoAssetFailed(tenantId, taskId, task.getErrorMessage());
        }
        log.info("AiTask polled: id={}, status={}", taskId, task.getStatus());
        return task;
    }

    @Override
    public AiTask submitTextToVideoAsync(Long projectId, Long tenantId, Long userId, Long episodeId,
                                         Long storyboardId, String imagePrompt, String videoPrompt,
                                         List<String> preImageKeys, int durationSeconds) {
        // 1. 立即创建 pending 任务并返回
        AiTask task = new AiTask();
        task.setTenantId(tenantId);
        task.setProjectId(projectId);
        task.setEpisodeId(episodeId);
        task.setStoryboardId(storyboardId);
        task.setTaskType(preImageKeys != null && !preImageKeys.isEmpty() ? "ref_image_to_video" : "text_to_video");
        task.setStatus("pending");
        task.setPrompt(imagePrompt);
        aiTaskMapper.insert(task);
        shotAssetService.recordPendingVideoAsset(
                tenantId,
                projectId,
                episodeId,
                storyboardId,
                task.getId(),
                videoPrompt != null ? videoPrompt : imagePrompt,
                seedanceService.getProvider(),
                seedanceService.getModelId(),
                preImageKeys != null ? preImageKeys : List.of(),
                null,
                null,
                null,
                java.util.Map.of("legacy", true, "durationSeconds", durationOrDefault(durationSeconds))
        );
        log.info("Pending task created: id={}, type={}", task.getId(), task.getTaskType());

        // 2. 后台异步执行（如有参考图则跳过 Seedream）
        List<String> keys = preImageKeys != null ? preImageKeys : java.util.Collections.emptyList();
        asyncProcessor.processTextToVideo(task.getId(), tenantId, userId, projectId, imagePrompt,
                videoPrompt != null ? videoPrompt : imagePrompt, keys, durationSeconds,
                "16:9", true, null);
        return task;
    }

    @Override
    public AiTask submitStructuredVideoTask(Long projectId, Long tenantId,
                                            StructuredVideoSubmission s) {
        // 角色：优先使用 characterImageKeys 列表，回退到旧单图字段
        List<String> characterKeys = s.getCharacterImageKeys();
        if (characterKeys == null || characterKeys.isEmpty()) {
            if (s.getCharacterImageKey() != null && !s.getCharacterImageKey().isBlank()) {
                characterKeys = java.util.List.of(s.getCharacterImageKey());
            } else {
                characterKeys = Collections.emptyList();
            }
        }
        boolean hasScene = s.getSceneImageKey() != null && !s.getSceneImageKey().isBlank();
        int propCount = s.getPropImageKeys() != null ? s.getPropImageKeys().size() : 0;
        List<String> frameImageKeys = frameImageKeys(s);
        int frameCount = frameImageKeys.size();
        boolean hasStyle = s.getStyleImageKey() != null && !s.getStyleImageKey().isBlank();

        // 至少要有一张参考图（角色/场景/道具/首尾帧任一），否则前端不应允许提交
        if (characterKeys.isEmpty() && !hasScene && propCount == 0 && frameCount == 0) {
            throw new BizException(ErrorCode.INVALID_PARAM, "至少需要一张参考图（角色/场景/道具/首尾帧任一）");
        }

        // 1. 拼装 final prompt + 图片顺序
        String finalPrompt;
        if (s.getFreeformPrompt() != null && !s.getFreeformPrompt().isBlank()) {
            finalPrompt = SeedancePromptBuilder.buildPromptFreeform(
                    s.getFreeformPrompt(), characterKeys.size(), hasScene, propCount, frameCount, hasStyle);
        } else {
            finalPrompt = SeedancePromptBuilder.buildPrompt(
                    new SeedancePromptBuilder.UserPromptParts(
                            s.getCharacterAction(),
                            s.getSceneEvent(),
                            s.getCameraMovement(),
                            s.getLightingAtmosphere(),
                            s.getVideoStyle()
                    ),
                    characterKeys.size(),
                    hasScene,
                    propCount,
                    frameCount,
                    hasStyle
            );
        }
        List<String> orderedKeys = SeedancePromptBuilder.buildOrderedImageKeys(
                characterKeys,
                s.getSceneImageKey(),
                s.getPropImageKeys(),
                frameImageKeys,
                s.getStyleImageKey()
        );

        // 2. 创建 pending 任务（落库结构化字段）
        AiTask task = new AiTask();
        task.setTenantId(tenantId);
        task.setProjectId(projectId);
        task.setEpisodeId(s.getEpisodeId());
        task.setStoryboardId(s.getStoryboardId());
        task.setTaskType("structured_video");
        task.setStatus("pending");
        task.setPrompt(finalPrompt);
        if (!characterKeys.isEmpty()) {
            task.setCharacterImageKey(characterKeys.get(0));
        }
        task.setCharacterImageKeys(toJson(characterKeys));
        task.setSceneImageKey(s.getSceneImageKey());
        task.setPropImageKeys(toJson(s.getPropImageKeys()));
        task.setStyleImageKey(s.getStyleImageKey());
        task.setUserPrompt(toJson(s.getUserPromptRaw()));
        task.setVideoDuration(s.getDurationSeconds());
        task.setVideoRatio(s.getRatio());
        task.setGenerateAudio(Boolean.TRUE.equals(s.getGenerateAudio()));
        aiTaskMapper.insert(task);
        persistStoryboardGenerationSelection(s, characterKeys, finalPrompt);
        shotAssetService.recordPendingVideoAsset(
                tenantId,
                projectId,
                s.getEpisodeId(),
                s.getStoryboardId(),
                task.getId(),
                finalPrompt,
                seedanceService.getProvider(),
                seedanceService.getModelId(),
                characterKeys,
                s.getSceneImageKey(),
                s.getPropImageKeys(),
                s.getStyleImageKey(),
                java.util.Map.of(
                        "durationSeconds", durationOrDefault(s.getDurationSeconds()),
                        "ratio", s.getRatio() != null ? s.getRatio() : "16:9",
                        "generateAudio", Boolean.TRUE.equals(s.getGenerateAudio()),
                        "frameImageKeys", frameImageKeys
                )
        );
        log.info("Structured video task created: id={}, characters={}, images={}, duration={}s, ratio={}",
                task.getId(), characterKeys.size(), orderedKeys.size(), s.getDurationSeconds(), s.getRatio());

        // 3. 复用 async processor：将拼接好的 prompt + 有序 key 列表交给后台
        int duration = durationOrDefault(s.getDurationSeconds());
        String ratio = s.getRatio() != null && !s.getRatio().isBlank() ? s.getRatio() : "16:9";
        boolean generateAudio = Boolean.TRUE.equals(s.getGenerateAudio());
        asyncProcessor.processTextToVideo(task.getId(), tenantId, s.getUserId(), projectId,
                finalPrompt, finalPrompt, orderedKeys, duration, ratio, generateAudio, null);
        return task;
    }

    private void persistStoryboardGenerationSelection(StructuredVideoSubmission s,
                                                       List<String> characterKeys,
                                                       String finalPrompt) {
        if (s.getStoryboardId() == null) {
            return;
        }
        Storyboard patch = new Storyboard();
        patch.setId(s.getStoryboardId());
        patch.setCharacterImageKeys(toJson(characterKeys));
        patch.setSceneImageKey(s.getSceneImageKey());
        patch.setPropImageKeys(toJson(s.getPropImageKeys()));
        patch.setStyleImageKey(s.getStyleImageKey());
        patch.setFirstFrameImageKey(s.getFirstFrameImageKey());
        patch.setLastFrameImageKey(s.getLastFrameImageKey());
        patch.setDurationSeconds(durationOrDefault(s.getDurationSeconds()));
        String editablePrompt = s.getFreeformPrompt() != null && !s.getFreeformPrompt().isBlank()
                ? s.getFreeformPrompt()
                : finalPrompt;
        patch.setVideoPrompt(editablePrompt);
        storyboardMapper.updateById(patch);
    }

    private List<String> frameImageKeys(StructuredVideoSubmission s) {
        List<String> keys = new ArrayList<>(2);
        if (s.getFirstFrameImageKey() != null && !s.getFirstFrameImageKey().isBlank()) {
            keys.add(s.getFirstFrameImageKey());
        }
        if (s.getLastFrameImageKey() != null && !s.getLastFrameImageKey().isBlank()) {
            keys.add(s.getLastFrameImageKey());
        }
        return keys;
    }

    private Integer durationOrDefault(Integer durationSeconds) {
        if (durationSeconds == null || durationSeconds <= 0) {
            return 5;
        }
        return Math.min(15, Math.max(5, durationSeconds));
    }

    private static String toJson(Object value) {
        if (value == null) return null;
        if (value instanceof List<?> list && list.isEmpty()) return null;
        try {
            return OBJECT_MAPPER.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            log.warn("Failed to serialize structured video field: {}", e.getMessage());
            return null;
        }
    }

    @Override
    public void deleteTask(Long taskId, Long tenantId) {
        AiTask task = aiTaskMapper.selectById(taskId);
        if (task == null || !tenantId.equals(task.getTenantId())) {
            throw new BizException(ErrorCode.NOT_FOUND, "任务不存在");
        }
        shotAssetService.deactivateBySourceTask(tenantId, taskId);
        aiTaskMapper.deleteById(taskId);
        log.info("AiTask deleted: id={}", taskId);
    }

    @Override
    public List<AiTask> listByProject(Long projectId, Long tenantId) {
        return aiTaskMapper.findByProject(projectId, tenantId);
    }
}
