<?php

namespace Tests\Feature;

use App\Contracts\VideoAvatarProvider;
use App\Exceptions\VideoDialogueException;
use App\Jobs\RenderVideoCreativeJob;
use App\Models\Avatar;
use App\Models\Creative;
use App\Models\User;
use App\Models\Voice;
use App\Services\AI\VertexSafetyFilteredException;
use App\Services\AI\VideoAvatarProviderFactory;
use Database\Seeders\AvatarSeeder;
use Database\Seeders\VoiceSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class RenderVideoCreativeJobTest extends TestCase
{
    use RefreshDatabase;

    private function makeCreative(int $duration = 8, string $ratio = '9:16'): Creative
    {
        $this->seed(AvatarSeeder::class);
        $this->seed(VoiceSeeder::class);

        return Creative::create([
            'user_id' => User::factory()->create()->id,
            'type' => 'video',
            'status' => 'generating',
            'source' => 'prompt',
            'prompt' => 'A video ad for our browser.',
            'ratio' => $ratio,
            'duration' => $duration,
            'avatar_id' => Avatar::first()->id,
            'voice_id' => Voice::first()->id,
            'script' => ['hook' => 'H', 'problem' => 'P', 'product' => 'Pr', 'benefits' => 'B', 'cta' => 'C'],
            'storyboard' => [['scene' => 'H'], ['scene' => 'P'], ['scene' => 'Pr'], ['scene' => 'B'], ['scene' => 'C']],
        ]);
    }

    public function test_it_renders_a_short_video_via_veo_and_marks_the_creative_completed(): void
    {
        Storage::fake('public');

        config([
            'ai.providers.gemini.key' => 'test-key',
            'ai.providers.gemini.url' => 'https://generativelanguage.googleapis.com/v1beta/',
            'creative.gemini_veo.poll_interval_seconds' => 0,
            'creative.gemini_veo.max_poll_attempts' => 3,
        ]);

        Http::fake([
            'generativelanguage.googleapis.com/v1beta/models/*:predictLongRunning' => Http::response([
                'name' => 'models/veo-3.0-generate-001/operations/abc123',
            ]),
            'generativelanguage.googleapis.com/v1beta/models/veo-3.0-generate-001/operations/abc123' => Http::response([
                'done' => true,
                'response' => [
                    'generateVideoResponse' => [
                        'generatedSamples' => [
                            ['video' => ['uri' => 'https://generativelanguage.googleapis.com/v1beta/files/abc123:download']],
                        ],
                    ],
                ],
            ]),
            'generativelanguage.googleapis.com/v1beta/files/abc123:download' => Http::response('FAKEVIDEOBYTES', 200),
        ]);

        $creative = $this->makeCreative(duration: 8);

        (new RenderVideoCreativeJob(
            $creative->id,
            $creative->avatar_id,
            $creative->voice_id,
            $creative->script,
            $creative->storyboard,
            $creative->duration,
            $creative->ratio,
        ))->handle(app(VideoAvatarProviderFactory::class));

        $creative->refresh();

        $this->assertSame('completed', $creative->status);
        $this->assertSame(8, $creative->provider['requested_duration']);
        $this->assertNotNull($creative->asset_path);
        $this->assertNull($creative->failure_reason);
        Storage::disk('public')->assertExists($creative->asset_path);
    }

    public function test_regenerate_uses_the_requested_duration_for_a_twenty_nine_second_vertex_asset(): void
    {
        Queue::fake();

        foreach ([
            ['provider' => 'gemini-veo-vertex', 'duration' => 29, 'extend_calls' => 3, 'requested_duration' => 30],
            ['provider' => 'gemini-veo-vertex', 'duration' => 29, 'extend_calls' => 3],
        ] as $metadata) {
            $creative = $this->makeCreative(duration: 29);
            $creative->update(['status' => 'completed', 'provider' => $metadata]);

            $this->actingAs($creative->user)
                ->post(route('creatives.regenerate', $creative))
                ->assertRedirect(route('creatives.show', $creative));

            Queue::assertPushed(RenderVideoCreativeJob::class, fn (RenderVideoCreativeJob $job): bool => $job->creativeId === $creative->id && $job->duration === 30);
        }
    }

    public function test_regenerate_rejects_retired_sora_duration_without_erasing_existing_video(): void
    {
        Queue::fake();

        $creative = $this->makeCreative(duration: 12);
        $creative->update([
            'status' => 'completed',
            'asset_path' => 'creatives/videos/original.mp4',
            'provider' => ['provider' => 'openai-sora', 'requested_duration' => 12],
        ]);

        $this->actingAs($creative->user)
            ->post(route('creatives.regenerate', $creative))
            ->assertRedirect(route('creatives.show', $creative))
            ->assertSessionHas('video_error');

        $creative->refresh();
        $this->assertSame('completed', $creative->status);
        $this->assertSame('creatives/videos/original.mp4', $creative->asset_path);
        Queue::assertNotPushed(RenderVideoCreativeJob::class);
    }

    public function test_old_queued_sora_job_fails_without_calling_the_retired_api(): void
    {
        Http::fake();
        $creative = $this->makeCreative(duration: 12);

        try {
            (new RenderVideoCreativeJob(
                $creative->id,
                $creative->avatar_id,
                $creative->voice_id,
                $creative->script,
                $creative->storyboard,
                12,
                $creative->ratio,
            ))->handle(app(VideoAvatarProviderFactory::class));
            $this->fail('Expected the retired duration to be rejected.');
        } catch (\InvalidArgumentException $exception) {
            $this->assertStringContainsString('12s is no longer supported', $exception->getMessage());
        }

        $this->assertSame('failed', $creative->refresh()->status);
        Http::assertNothingSent();
    }

    public function test_it_marks_the_creative_failed_when_the_provider_throws(): void
    {
        config(['ai.providers.gemini.key' => null]);

        $creative = $this->makeCreative(duration: 8);

        try {
            (new RenderVideoCreativeJob(
                $creative->id,
                $creative->avatar_id,
                $creative->voice_id,
                $creative->script,
                $creative->storyboard,
                $creative->duration,
                $creative->ratio,
            ))->handle(app(VideoAvatarProviderFactory::class));
            $this->fail('Expected exception was not thrown.');
        } catch (\Throwable $e) {
            // provider throws ProviderNotConfiguredException when the key is missing
        }

        $creative->refresh();

        $this->assertSame('failed', $creative->status);
        $this->assertNotNull($creative->failure_reason);
    }

    public function test_it_rejects_overlong_dialogue_before_submitting_a_video_request(): void
    {
        config([
            'ai.providers.gemini.key' => 'test-key',
            'creative.spoken_words_per_second' => 1.7,
        ]);
        Http::fake();

        $creative = $this->makeCreative(duration: 8);
        $script = $creative->script;
        $script['spoken_line'] = implode(' ', array_fill(0, 12, 'word'));

        try {
            (new RenderVideoCreativeJob(
                $creative->id,
                $creative->avatar_id,
                $creative->voice_id,
                $script,
                $creative->storyboard,
                $creative->duration,
                $creative->ratio,
            ))->handle(app(VideoAvatarProviderFactory::class));
            $this->fail('Expected dialogue validation to fail.');
        } catch (VideoDialogueException $exception) {
            $this->assertStringContainsString('too long', $exception->getMessage());
        }

        $this->assertSame('failed', $creative->refresh()->status);
        $this->assertStringContainsString('too long', $creative->failure_reason);
        Http::assertNothingSent();
    }

    public function test_vertex_safety_filter_shows_a_useful_failure_instead_of_a_generic_error(): void
    {
        $creative = $this->makeCreative(duration: 15);
        $exception = new VertexSafetyFilteredException('Vertex AI usage guidelines (support code 15236754)');
        $provider = $this->createMock(VideoAvatarProvider::class);
        $provider->method('render')->willThrowException($exception);
        $factory = $this->createMock(VideoAvatarProviderFactory::class);
        $factory->method('forDuration')->willReturn($provider);
        $job = new RenderVideoCreativeJob(
            $creative->id,
            $creative->avatar_id,
            $creative->voice_id,
            $creative->script,
            $creative->storyboard,
            $creative->duration,
            $creative->ratio,
        );

        try {
            $job->handle($factory);
            $this->fail('Expected Vertex to reject the render.');
        } catch (VertexSafetyFilteredException $caught) {
            $this->assertSame($exception, $caught);
        }

        $this->assertSame('failed', $creative->refresh()->status);
        $this->assertSame('The video prompt was rejected by the provider safety filter. Please revise the description and try again.', $creative->failure_reason);

        $creative->update(['status' => 'processing', 'failure_reason' => null]);
        $job->failed($exception);
        $this->assertSame('The video prompt was rejected by the provider safety filter. Please revise the description and try again.', $creative->refresh()->failure_reason);
    }

    public function test_it_is_a_no_op_when_the_creative_is_already_completed(): void
    {
        $creative = $this->makeCreative(duration: 8);
        $creative->update(['status' => 'completed', 'asset_path' => 'creatives/videos/already-done.mp4']);

        (new RenderVideoCreativeJob(
            $creative->id,
            $creative->avatar_id,
            $creative->voice_id,
            $creative->script,
            $creative->storyboard,
            $creative->duration,
            $creative->ratio,
        ))->handle(app(VideoAvatarProviderFactory::class));

        $creative->refresh();

        $this->assertSame('completed', $creative->status);
        $this->assertSame('creatives/videos/already-done.mp4', $creative->asset_path);
    }
}
