package com.yaoai.pipeline.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.yaoai.billing.dto.BillingChargeRequest;
import com.yaoai.billing.service.BillingService;
import com.yaoai.domain.entity.AiTask;
import com.yaoai.domain.entity.AssemblyTask;
import com.yaoai.domain.mapper.AiTaskMapper;
import com.yaoai.domain.mapper.AssemblyTaskMapper;
import com.yaoai.media.service.FfmpegService;
import com.yaoai.pipeline.service.AssemblyPipelineService;
import com.yaoai.storage.service.TosService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

@Slf4j
@Service
public class AssemblyPipelineServiceImpl implements AssemblyPipelineService {

    private final AssemblyTaskMapper assemblyTaskMapper;
    private final AiTaskMapper aiTaskMapper;
    private final FfmpegService ffmpegService;
    private final TosService tosService;
    private final BillingService billingService;
    private final Executor assemblyExecutor;

    public AssemblyPipelineServiceImpl(
            AssemblyTaskMapper assemblyTaskMapper,
            AiTaskMapper aiTaskMapper,
            FfmpegService ffmpegService,
            TosService tosService,
            BillingService billingService,
            @Qualifier("assemblyExecutor") Executor assemblyExecutor) {
        this.assemblyTaskMapper = assemblyTaskMapper;
        this.aiTaskMapper = aiTaskMapper;
        this.ffmpegService = ffmpegService;
        this.tosService = tosService;
        this.billingService = billingService;
        this.assemblyExecutor = assemblyExecutor;
    }

    @Override
    public AssemblyTask startAssembly(Long projectId, Long episodeId, Long tenantId) {
        AssemblyTask task = new AssemblyTask();
        task.setTenantId(tenantId);
        task.setProjectId(projectId);
        task.setEpisodeId(episodeId);
        task.setStatus("pending");
        task.setShotCount(0);
        assemblyTaskMapper.insert(task);

        Long taskId = task.getId();
        CompletableFuture.runAsync(() -> doAssemble(taskId, episodeId), assemblyExecutor);
        return task;
    }

    @Override
    public AssemblyTask getLatest(Long episodeId, Long tenantId) {
        return assemblyTaskMapper.findLatestByEpisode(episodeId, tenantId);
    }

    @Override
    public String signedDownloadUrl(AssemblyTask task) {
        if (task == null || task.getResultTosKey() == null) return null;
        return tosService.publicUrl(task.getResultTosKey());
    }

    private void doAssemble(Long assemblyTaskId, Long episodeId) {
        AssemblyTask task = assemblyTaskMapper.selectById(assemblyTaskId);
        task.setStatus("running");
        assemblyTaskMapper.updateById(task);

        Path outputFile = null;
        try {
            List<AiTask> shots = aiTaskMapper.findSucceededByEpisodeOrdered(episodeId, task.getTenantId());
            if (shots.isEmpty()) {
                fail(task, "没有可用的视频片段（需先生成每个镜头的视频）");
                return;
            }

            List<String> urls = shots.stream()
                    .map(AiTask::getResultVideoUrl)
                    .filter(u -> u != null && !u.isBlank())
                    .toList();

            if (urls.isEmpty()) {
                fail(task, "所有镜头的视频 URL 为空");
                return;
            }

            task.setShotCount(urls.size());
            assemblyTaskMapper.updateById(task);

            outputFile = ffmpegService.concatVideos(urls);

            byte[] bytes = Files.readAllBytes(outputFile);
            String tosKey = "assembly/" + task.getTenantId() + "/" + episodeId + "/" + assemblyTaskId + ".mp4";
            try (var is = new java.io.ByteArrayInputStream(bytes)) {
                tosService.upload(tosKey, is, bytes.length, "video/mp4");
            }

            task.setResultTosKey(tosKey);
            task.setStatus("succeeded");
            assemblyTaskMapper.updateById(task);
            billingService.charge(BillingChargeRequest.builder()
                    .tenantId(task.getTenantId())
                    .userId(StpUtil.getLoginIdAsLong())
                    .projectId(task.getProjectId())
                    .operation("assembly")
                    .modality("COMPOSE")
                    .modelProvider("ffmpeg")
                    .modelId("ffmpeg-compose")
                    .modelName("FFmpeg 视频合成")
                    .unitCount(1)
                    .refId(String.valueOf(assemblyTaskId))
                    .build());
            log.info("Assembly task {} succeeded, tosKey={}", assemblyTaskId, tosKey);

        } catch (IOException | InterruptedException e) {
            log.error("Assembly task {} failed", assemblyTaskId, e);
            fail(task, e.getMessage());
        } finally {
            if (outputFile != null) ffmpegService.cleanup(outputFile);
        }
    }

    private void fail(AssemblyTask task, String msg) {
        task.setStatus("failed");
        task.setErrorMessage(msg);
        assemblyTaskMapper.updateById(task);
    }
}
