<?php

namespace App\Jobs;

use App\Models\Avatar;
use App\Models\Creative;
use App\Models\Voice;
use App\Services\AI\VideoAvatarProviderFactory;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Throwable;

/**
 * Runs video rendering (Veo or Sora, whichever VideoAvatarProviderFactory picks
 * for the requested duration) out of the web request. Vendor render calls can
 * take minutes (Sora especially, once an extension call is needed), which
 * reliably exceeds nginx/php-fpm request timeouts if done synchronously in
 * ChatWizard::confirmGenerate().
 */
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;

    /** Generous ceiling for Sora base + extension polling; the worker process itself, not this value, is what must stay alive. */
    public int $timeout = 900;

    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']);

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

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

            $creative->update([
                'status' => 'completed',
                'asset_path' => $result->assetPath,
                'thumbnail_path' => $result->thumbnailPath,
                'provider' => $result->meta,
                // Sora in particular can deliver a clip longer (or, if the extension step
                // fell back to the base clip, shorter) than requested — store what was
                // actually rendered, not the request, so this never disagrees with the asset.
                'duration' => $result->meta['duration'] ?? $this->duration,
                'failure_reason' => null,
            ]);
        } 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' => $e]);

            $creative->update([
                'status' => 'failed',
                'failure_reason' => 'Video generation failed. Please try again.',
            ]);

            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' => $exception]);
        }

        $creative->update([
            'status' => 'failed',
            'failure_reason' => 'Video generation failed. Please try again.',
        ]);
    }
}
