<?php

namespace App\Jobs;

use App\Exceptions\VideoDialogueException;
use App\Exceptions\VideoPromptFilteredException;
use App\Models\Avatar;
use App\Models\Creative;
use App\Models\Voice;
use App\Services\AI\VideoAvatarProviderFactory;
use App\Support\VideoDialogue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Throwable;

/** Runs Veo video rendering outside the web request. */
class RenderVideoCreativeJob implements ShouldQueue
{
    use Queueable;

    /** Not safely re-runnable: a retry would start a brand new (billable) vendor render rather than resuming the old one. */
    public int $tries = 1;

    /** Allows Vertex Veo base generation and multiple extensions to finish. */
    public int $timeout = 1500;

    public function __construct(
        public int $creativeId,
        public int $avatarId,
        public int $voiceId,
        public array $script,
        public array $storyboard,
        public int $duration,
        public string $ratio,
    ) {}

    public function handle(VideoAvatarProviderFactory $providerFactory): void
    {
        $creative = Creative::findOrFail($this->creativeId);

        if ($creative->status === 'completed') {
            return; // already rendered; safe to retry without duplicating work
        }

        $creative->update([
            'status' => 'processing',
            'render_progress' => 3,
            'render_stage' => 'Preparing your video',
        ]);

        try {
            $avatar = Avatar::findOrFail($this->avatarId);
            $voice = Voice::findOrFail($this->voiceId);
            $provider = $providerFactory->forDuration($this->duration);

            if (filled($this->script['spoken_line'] ?? null)) {
                VideoDialogue::assertFits((string) $this->script['spoken_line'], $this->duration);
            }

            $lastProgress = 3;
            $reportProgress = static function (int $percent, string $stage) use ($creative, &$lastProgress): void {
                $lastProgress = max($lastProgress, min(99, max(0, $percent)));

                Creative::whereKey($creative->id)->update([
                    'render_progress' => $lastProgress,
                    'render_stage' => $stage,
                ]);
            };

            $context = [
                'project_name' => $creative->project_name,
                'brief' => $creative->brief,
                'product_url' => $creative->product_url,
            ];

            $result = $provider->render($avatar, $voice, $this->script, $this->storyboard, $this->duration, $this->ratio, $creative->style, $reportProgress, $context);

            $creative->update([
                'status' => 'completed',
                'asset_path' => $result->assetPath,
                'thumbnail_path' => $result->thumbnailPath,
                'provider' => [...$result->meta, 'requested_duration' => $this->duration],
                // Vertex extensions may produce a slightly different duration than requested.
                'duration' => $result->meta['duration'] ?? $this->duration,
                'failure_reason' => null,
                'render_progress' => 100,
                'render_stage' => 'Ready',
            ]);
        } catch (Throwable $e) {
            // Log the real cause; never surface a raw vendor/exception message in the UI.
            logger()->error('Video render job failed', ['creative_id' => $this->creativeId, 'exception_type' => $e::class]);

            $creative->update([
                'status' => 'failed',
                'failure_reason' => match (true) {
                    $e instanceof VideoDialogueException => $e->getMessage(),
                    $e instanceof VideoPromptFilteredException => 'The video prompt was rejected by the provider safety filter. Please revise the description and try again.',
                    default => 'Video generation failed. Please try again.',
                },
                'render_stage' => 'Generation failed',
            ]);

            throw $e;
        }
    }

    /**
     * Covers the case handle()'s own catch can't: the worker process itself being
     * killed (timeout, restart, OOM) mid-render. Without this, tries=1 + no catch
     * running leaves the creative stuck on "processing" forever with no way out in the UI.
     */
    public function failed(?Throwable $exception): void
    {
        $creative = Creative::find($this->creativeId);

        if ($creative === null || $creative->status === 'completed') {
            return;
        }

        if ($exception !== null) {
            logger()->error('Video render job failed permanently', ['creative_id' => $this->creativeId, 'exception_type' => $exception::class]);
        }

        $creative->update([
            'status' => 'failed',
            'failure_reason' => match (true) {
                $exception instanceof VideoDialogueException => $exception->getMessage(),
                $exception instanceof VideoPromptFilteredException => 'The video prompt was rejected by the provider safety filter. Please revise the description and try again.',
                default => 'Video generation failed. Please try again.',
            },
            'render_stage' => 'Generation failed',
        ]);
    }
}
