<?php

namespace App\Services\Video;

use Illuminate\Support\Facades\Log;

class TutorialAssemblyService
{
    private VideoAssemblyService $videoAssembly;

    public function __construct(VideoAssemblyService $videoAssembly)
    {
        $this->videoAssembly = $videoAssembly;
    }

    /**
     * 어노테이션 이미지들 → MP4 영상 합성
     *
     * @param  array  $slidePaths  슬라이드 이미지 경로 배열
     * @param  array  $durations  각 슬라이드 표시 시간(초) 배열
     * @param  string|null  $narrationPath  나레이션 오디오 경로
     * @param  string|null  $bgmPath  BGM 오디오 경로
     * @param  string  $subtitlePath  ASS 자막 파일 경로
     * @param  string  $outputPath  최종 MP4 출력 경로
     * @return string|null 성공 시 출력 경로
     */
    public function assembleFromImages(
        array $slidePaths,
        array $durations,
        ?string $narrationPath,
        ?string $bgmPath,
        ?string $subtitlePath,
        string $outputPath
    ): ?string {
        if (empty($slidePaths)) {
            Log::error('TutorialAssembly: 슬라이드가 없습니다');

            return null;
        }

        $dir = dirname($outputPath);
        if (! is_dir($dir)) {
            mkdir($dir, 0755, true);
        }

        // Step 1: 이미지 시퀀스 → 무음 MP4 (crossfade 포함)
        $silentVideoPath = "{$dir}/silent_video.mp4";
        $silentVideo = $this->imagesToVideo($slidePaths, $durations, $silentVideoPath);

        if (! $silentVideo) {
            Log::error('TutorialAssembly: 이미지→영상 변환 실패');

            return null;
        }

        // Step 2: 무음 영상 + 나레이션 + BGM + 자막 합성
        $result = $this->videoAssembly->assemble(
            $silentVideo,
            $narrationPath,
            $bgmPath,
            $subtitlePath,
            $outputPath
        );

        // 임시 파일 정리
        @unlink($silentVideoPath);

        return $result;
    }

    /**
     * 이미지 시퀀스를 crossfade 트랜지션으로 영상 변환
     */
    private function imagesToVideo(array $slidePaths, array $durations, string $outputPath): ?string
    {
        $count = count($slidePaths);

        if ($count === 0) {
            return null;
        }

        // 이미지 1장인 경우 단순 변환
        if ($count === 1) {
            $duration = $durations[0] ?? 8;
            $cmd = sprintf(
                'ffmpeg -y -loop 1 -i %s -c:v libx264 -t %d -pix_fmt yuv420p -vf "scale=1920:1080" -r 30 %s 2>&1',
                escapeshellarg($slidePaths[0]),
                $duration,
                escapeshellarg($outputPath)
            );

            exec($cmd, $output, $returnCode);

            if ($returnCode !== 0) {
                Log::error('TutorialAssembly: 단일 이미지 변환 실패', [
                    'output' => implode("\n", array_slice($output, -10)),
                ]);

                return null;
            }

            return $outputPath;
        }

        // 여러 장인 경우: 각 이미지를 개별 영상으로 만든 후 xfade로 결합
        $fadeDuration = 0.5;
        $dir = dirname($outputPath);
        $clipPaths = [];

        // Step 1: 각 이미지를 개별 클립으로 변환
        foreach ($slidePaths as $i => $slidePath) {
            $duration = $durations[$i] ?? 8;
            $clipPath = "{$dir}/clip_{$i}.mp4";

            $cmd = sprintf(
                'ffmpeg -y -loop 1 -i %s -c:v libx264 -t %s -pix_fmt yuv420p -vf "scale=1920:1080" -r 30 %s 2>&1',
                escapeshellarg($slidePath),
                $duration,
                escapeshellarg($clipPath)
            );

            exec($cmd, $output, $returnCode);

            if ($returnCode !== 0) {
                Log::error("TutorialAssembly: 클립 {$i} 변환 실패", [
                    'output' => implode("\n", array_slice($output, -10)),
                ]);

                // 실패 시 crossfade 없이 fallback
                return $this->imagesToVideoSimple($slidePaths, $durations, $outputPath);
            }

            $clipPaths[] = $clipPath;
        }

        // Step 2: xfade 필터로 crossfade 결합
        $result = $this->xfadeConcat($clipPaths, $durations, $fadeDuration, $outputPath);

        // 임시 클립 정리
        foreach ($clipPaths as $path) {
            @unlink($path);
        }

        return $result;
    }

    /**
     * xfade 필터를 사용한 crossfade 결합
     */
    private function xfadeConcat(array $clipPaths, array $durations, float $fadeDuration, string $outputPath): ?string
    {
        $count = count($clipPaths);

        if ($count < 2) {
            return null;
        }

        // 2개인 경우 단순 xfade
        if ($count === 2) {
            $offset = max(0, ($durations[0] ?? 8) - $fadeDuration);
            $cmd = sprintf(
                'ffmpeg -y -i %s -i %s -filter_complex "[0:v][1:v]xfade=transition=fade:duration=%s:offset=%s[v]" -map "[v]" -c:v libx264 -preset fast -crf 23 -r 30 -pix_fmt yuv420p %s 2>&1',
                escapeshellarg($clipPaths[0]),
                escapeshellarg($clipPaths[1]),
                $fadeDuration,
                $offset,
                escapeshellarg($outputPath)
            );

            exec($cmd, $output, $returnCode);

            if ($returnCode !== 0) {
                Log::warning('TutorialAssembly: xfade 실패, concat fallback', [
                    'output' => implode("\n", array_slice($output, -10)),
                ]);

                return $this->simpleConcatClips($clipPaths, $outputPath);
            }

            return $outputPath;
        }

        // 3개 이상: 체인 xfade
        $inputs = '';
        foreach ($clipPaths as $path) {
            $inputs .= '-i '.escapeshellarg($path).' ';
        }

        $filter = '';
        $cumulativeOffset = 0;

        for ($i = 0; $i < $count - 1; $i++) {
            $cumulativeOffset += ($durations[$i] ?? 8) - $fadeDuration;

            $inputA = ($i === 0) ? '[0:v]' : "[v{$i}]";
            $inputB = '['.($i + 1).':v]';
            $outputLabel = ($i === $count - 2) ? '[v]' : '[v'.($i + 1).']';

            $filter .= "{$inputA}{$inputB}xfade=transition=fade:duration={$fadeDuration}:offset={$cumulativeOffset}{$outputLabel}";

            if ($i < $count - 2) {
                $filter .= ';';
            }
        }

        $cmd = sprintf(
            'ffmpeg -y %s -filter_complex "%s" -map "[v]" -c:v libx264 -preset fast -crf 23 -r 30 -pix_fmt yuv420p %s 2>&1',
            $inputs,
            $filter,
            escapeshellarg($outputPath)
        );

        exec($cmd, $output, $returnCode);

        if ($returnCode !== 0) {
            Log::warning('TutorialAssembly: 체인 xfade 실패, concat fallback', [
                'output' => implode("\n", array_slice($output, -10)),
            ]);

            return $this->simpleConcatClips($clipPaths, $outputPath);
        }

        return $outputPath;
    }

    /**
     * 단순 concat fallback (crossfade 실패 시)
     */
    private function simpleConcatClips(array $clipPaths, string $outputPath): ?string
    {
        $dir = dirname($outputPath);
        $listFile = "{$dir}/concat_tutorial.txt";
        $listContent = '';

        foreach ($clipPaths as $path) {
            $listContent .= 'file '.escapeshellarg($path)."\n";
        }

        file_put_contents($listFile, $listContent);

        $cmd = sprintf(
            'ffmpeg -y -f concat -safe 0 -i %s -c copy %s 2>&1',
            escapeshellarg($listFile),
            escapeshellarg($outputPath)
        );

        exec($cmd, $output, $returnCode);
        @unlink($listFile);

        if ($returnCode !== 0) {
            Log::error('TutorialAssembly: concat fallback도 실패', [
                'output' => implode("\n", array_slice($output, -10)),
            ]);

            return null;
        }

        return $outputPath;
    }

    /**
     * crossfade 없이 단순 이미지→영상 변환 fallback
     */
    private function imagesToVideoSimple(array $slidePaths, array $durations, string $outputPath): ?string
    {
        $dir = dirname($outputPath);
        $clipPaths = [];

        foreach ($slidePaths as $i => $slidePath) {
            $duration = $durations[$i] ?? 8;
            $clipPath = "{$dir}/simple_clip_{$i}.mp4";

            $cmd = sprintf(
                'ffmpeg -y -loop 1 -i %s -c:v libx264 -t %d -pix_fmt yuv420p -vf "scale=1920:1080" -r 30 %s 2>&1',
                escapeshellarg($slidePath),
                $duration,
                escapeshellarg($clipPath)
            );

            exec($cmd, $output, $returnCode);

            if ($returnCode === 0) {
                $clipPaths[] = $clipPath;
            }
        }

        if (empty($clipPaths)) {
            return null;
        }

        $result = $this->simpleConcatClips($clipPaths, $outputPath);

        foreach ($clipPaths as $path) {
            @unlink($path);
        }

        return $result;
    }
}
