<?php

namespace App\Jobs;

use App\Models\VideoGeneration;
use App\Services\GoogleCloudStorageService;
use App\Services\Video\BgmService;
use App\Services\Video\GeminiScriptService;
use App\Services\Video\TtsService;
use App\Services\Video\VeoVideoService;
use App\Services\Video\VideoAssemblyService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;

class VideoGenerationJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $timeout = 1800; // 30분

    public int $tries = 1;

    private int $videoGenerationId;

    private ?string $selectedTitle;

    private ?array $customScenario;

    public function __construct(int $videoGenerationId, ?string $selectedTitle = null, ?array $customScenario = null)
    {
        $this->videoGenerationId = $videoGenerationId;
        $this->selectedTitle = $selectedTitle;
        $this->customScenario = $customScenario;
        $this->onQueue('mng');
    }

    public function handle(
        GeminiScriptService $gemini,
        VeoVideoService $veo,
        TtsService $tts,
        BgmService $bgm,
        VideoAssemblyService $assembly,
        GoogleCloudStorageService $gcs
    ): void {
        $video = VideoGeneration::withoutGlobalScopes()->find($this->videoGenerationId);

        if (! $video) {
            Log::error('VideoGenerationJob: 레코드를 찾을 수 없음', ['id' => $this->videoGenerationId]);

            return;
        }

        $workDir = storage_path("app/video_gen/{$video->id}");
        if (! is_dir($workDir)) {
            mkdir($workDir, 0755, true);
        }

        $totalCost = 0.0;

        try {
            // === Step 1: 시나리오 생성 ===
            $video->updateProgress(VideoGeneration::STATUS_SCENARIO_READY, 5, '시나리오 생성 중...');

            if ($this->customScenario) {
                $scenario = $this->customScenario;
            } else {
                $title = $this->selectedTitle ?? $video->title;
                $scenario = $gemini->generateScenario($title, $video->keyword);

                if (empty($scenario) || empty($scenario['scenes'])) {
                    $video->markFailed('시나리오 생성 실패');

                    return;
                }
            }

            $video->update([
                'scenario' => $scenario,
                'title' => $scenario['title'] ?? $video->title,
            ]);

            $scenes = $scenario['scenes'] ?? [];
            $totalCost += 0.001; // Gemini 비용

            // === Step 2: 나레이션 생성 ===
            $video->updateProgress(VideoGeneration::STATUS_GENERATING_TTS, 15, '나레이션 생성 중...');

            $narrationPaths = $tts->synthesizeScenes($scenes, $workDir);

            if (empty($narrationPaths)) {
                $video->markFailed('나레이션 생성 실패');

                return;
            }

            $totalCost += 0.01; // TTS 비용

            // === Step 3: 영상 클립 생성 ===
            $video->updateProgress(VideoGeneration::STATUS_GENERATING_CLIPS, 20, '영상 클립 생성 요청 중...');

            $clipPaths = [];
            $operations = [];

            // 모든 장면의 영상 생성 요청 (비동기)
            foreach ($scenes as $scene) {
                $sceneNum = $scene['scene_number'];
                $prompt = $scene['visual_prompt'] ?? '';
                $duration = $scene['duration'] ?? 8;

                $video->updateProgress(
                    VideoGeneration::STATUS_GENERATING_CLIPS,
                    20 + (int) (($sceneNum / count($scenes)) * 10),
                    "영상 클립 생성 요청 중 ({$sceneNum}/".count($scenes).')'
                );

                $result = $veo->generateClip($prompt, $duration);

                if (! $result) {
                    $video->markFailed("장면 {$sceneNum} 영상 생성 요청 실패");

                    return;
                }

                $operations[$sceneNum] = $result['operationName'];
            }

            $totalCost += count($scenes) * 1.20; // Veo 비용 (Fast 기준 8초당 $1.20)

            // 모든 영상 클립 완료 대기
            $skippedScenes = [];

            foreach ($operations as $sceneNum => $operationName) {
                $video->updateProgress(
                    VideoGeneration::STATUS_GENERATING_CLIPS,
                    30 + (int) (($sceneNum / count($scenes)) * 40),
                    "영상 클립 생성 대기 중 ({$sceneNum}/".count($scenes).')'
                );

                $result = $veo->waitAndSave(
                    $operationName,
                    "{$workDir}/clip_{$sceneNum}.mp4"
                );

                if ($result['path']) {
                    $clipPaths[$sceneNum] = $result['path'];

                    continue;
                }

                // 실패 시: RAI 필터링이면 프롬프트 수정 후 재시도
                Log::warning("VideoGenerationJob: 장면 {$sceneNum} 실패, 재시도 중", [
                    'error' => $result['error'],
                ]);

                $video->updateProgress(
                    VideoGeneration::STATUS_GENERATING_CLIPS,
                    30 + (int) (($sceneNum / count($scenes)) * 40),
                    "장면 {$sceneNum} 재시도 중 (프롬프트 수정)..."
                );

                $scene = $scenes[$sceneNum - 1] ?? null;
                $retryPrompt = $this->makeSafePrompt($scene['visual_prompt'] ?? '');
                $retryResult = $veo->generateClip($retryPrompt, $scene['duration'] ?? 8);

                if ($retryResult) {
                    $retryWait = $veo->waitAndSave(
                        $retryResult['operationName'],
                        "{$workDir}/clip_{$sceneNum}.mp4"
                    );

                    if ($retryWait['path']) {
                        $clipPaths[$sceneNum] = $retryWait['path'];
                        Log::info("VideoGenerationJob: 장면 {$sceneNum} 재시도 성공");

                        continue;
                    }
                }

                // 재시도도 실패 → 건너뛰기
                $skippedScenes[] = $sceneNum;
                Log::warning("VideoGenerationJob: 장면 {$sceneNum} 최종 실패, 건너뛰기", [
                    'error' => $result['error'],
                ]);
            }

            // 성공한 클립이 절반 미만이면 전체 실패
            if (count($clipPaths) < ceil(count($scenes) / 2)) {
                $video->markFailed('영상 클립 생성 실패 (성공: '.count($clipPaths).'/'.count($scenes).')');

                return;
            }

            if (! empty($skippedScenes)) {
                Log::info('VideoGenerationJob: 건너뛴 장면', ['scenes' => $skippedScenes]);
            }

            // 장면 순서로 정렬
            ksort($clipPaths);

            $video->update(['clips_data' => $clipPaths]);

            // 건너뛴 장면 필터링: 나레이션/자막도 성공한 클립에 맞춤
            $activeSceneNums = array_keys($clipPaths);
            $activeScenes = array_filter($scenes, fn ($s) => in_array($s['scene_number'], $activeSceneNums));
            $activeScenes = array_values($activeScenes);

            // 나레이션 키를 장면 번호로 유지 (concatNarrations에서 scene_number로 매칭)
            $activeNarrationPaths = [];
            foreach ($activeSceneNums as $num) {
                if (isset($narrationPaths[$num])) {
                    $activeNarrationPaths[$num] = $narrationPaths[$num];
                }
            }

            // === Step 4: BGM 생성/선택 ===
            $video->updateProgress(VideoGeneration::STATUS_GENERATING_BGM, 75, 'AI 배경음악 생성 중...');

            $bgmMood = $scenario['bgm_mood'] ?? 'upbeat';
            $totalDuration = array_sum(array_column($activeScenes, 'duration'));

            // 1순위: Google Lyria AI 배경음악 생성
            $bgmPath = $bgm->generateWithLyria($bgmMood, $totalDuration, "{$workDir}/bgm.mp3");
            if ($bgmPath) {
                $totalCost += 0.06; // Lyria 비용 ($0.06/30초)
            }

            // 2순위: 프리셋 BGM 파일
            if (! $bgmPath) {
                $bgmPath = $bgm->select($bgmMood, "{$workDir}/bgm.mp3");
            }

            // 3순위: FFmpeg 앰비언트 BGM
            if (! $bgmPath) {
                $bgmPath = $bgm->generateAmbient($bgmMood, $totalDuration, "{$workDir}/bgm.mp3");
            }

            // === Step 5: 최종 합성 ===
            $video->updateProgress(VideoGeneration::STATUS_ASSEMBLING, 80, '영상 합성 중...');

            // 5-1. 클립 결합
            $concatPath = "{$workDir}/concat.mp4";
            $concatResult = $assembly->concatClips(array_values($clipPaths), $concatPath);

            if (! $concatResult) {
                $video->markFailed('영상 클립 결합 실패');

                return;
            }

            // 5-2. 나레이션 결합 (성공한 장면만)
            $narrationConcatPath = "{$workDir}/narration_full.mp3";
            $assembly->concatNarrations($activeNarrationPaths, $activeScenes, $narrationConcatPath);

            // 5-3. 자막 생성 (실제 TTS 오디오 길이 기반 싱크)
            $narrationDurations = [];
            foreach ($activeNarrationPaths as $sceneNum => $narPath) {
                $dur = $assembly->getAudioDuration($narPath);
                if ($dur > 0) {
                    $narrationDurations[$sceneNum] = $dur;
                }
            }

            $subtitlePath = "{$workDir}/subtitles.ass";
            $assembly->generateAssSubtitle($activeScenes, $subtitlePath, $narrationDurations);

            // 5-4. 최종 합성
            $video->updateProgress(VideoGeneration::STATUS_ASSEMBLING, 90, '최종 합성 중...');

            $finalPath = "{$workDir}/final_{$video->id}.mp4";
            $result = $assembly->assemble(
                $concatPath,
                file_exists($narrationConcatPath) ? $narrationConcatPath : null,
                $bgmPath,
                $subtitlePath,
                $finalPath
            );

            if (! $result) {
                $video->markFailed('최종 영상 합성 실패');

                return;
            }

            // === GCS 업로드 ===
            $gcsPath = null;

            if ($gcs->isAvailable()) {
                $video->updateProgress(VideoGeneration::STATUS_ASSEMBLING, 95, 'GCS 업로드 중...');

                $objectName = "video_gen/{$video->id}/final_{$video->id}.mp4";
                $gcsUri = $gcs->upload($finalPath, $objectName);

                if ($gcsUri) {
                    $gcsPath = $objectName;
                    Log::info('VideoGenerationJob: GCS 업로드 성공', [
                        'id' => $video->id,
                        'gcs_path' => $objectName,
                    ]);
                } else {
                    Log::warning('VideoGenerationJob: GCS 업로드 실패, 로컬 파일로 계속', [
                        'id' => $video->id,
                    ]);
                }
            }

            // === 완료 ===
            $stepMsg = empty($skippedScenes)
                ? '완료'
                : '완료 (장면 '.implode(',', $skippedScenes).' 건너뜀)';

            $video->update([
                'status' => VideoGeneration::STATUS_COMPLETED,
                'progress' => 100,
                'current_step' => $stepMsg,
                'output_path' => $finalPath,
                'gcs_path' => $gcsPath,
                'cost_usd' => $totalCost,
            ]);

            // 중간 파일 정리
            $assembly->cleanup($workDir);

            Log::info('VideoGenerationJob: 영상 생성 완료', [
                'id' => $video->id,
                'output' => $finalPath,
                'gcs_path' => $gcsPath,
                'cost' => $totalCost,
            ]);
        } catch (\Exception $e) {
            Log::error('VideoGenerationJob: 예외 발생', [
                'id' => $video->id,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            $video->markFailed($e->getMessage());
        }
    }

    public function failed(\Throwable $exception): void
    {
        $video = VideoGeneration::withoutGlobalScopes()->find($this->videoGenerationId);
        $video?->markFailed('Job 실패: '.$exception->getMessage());
    }

    /**
     * RAI 필터링 방지를 위해 프롬프트를 안전하게 수정
     */
    private function makeSafePrompt(string $prompt): string
    {
        // 사람 관련 키워드를 풍경/오브젝트 중심으로 대체
        $prompt = preg_replace('/\b(woman|man|girl|boy|person|people|her|his|she|he)\b/i', 'subject', $prompt);

        // 안전 키워드 추가
        return 'Safe for all audiences. Professional stock footage style. '.$prompt;
    }
}
