<?php

namespace Tests\Unit\Services;

use App\Exceptions\ProviderNotConfiguredException;
use App\Models\Avatar;
use App\Models\Voice;
use App\Services\AI\OpenAiSoraVideoAvatarProvider;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use RuntimeException;
use Tests\TestCase;

class OpenAiSoraVideoAvatarProviderTest extends TestCase
{
    private function script(): array
    {
        return [
            'hook' => 'Tired of slow browsing?',
            'problem' => 'Ads slow you down.',
            'product' => 'Chromium Browser',
            'benefits' => 'Faster pages, no clutter.',
            'cta' => 'Download free today.',
        ];
    }

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

        $this->expectException(ProviderNotConfiguredException::class);

        (new OpenAiSoraVideoAvatarProvider)->render(
            new Avatar(['name' => 'Ava', 'gender' => 'female', 'accent' => 'American']),
            new Voice(['name' => 'Kore', 'tone' => 'warm']),
            $this->script(),
            [['scene' => 'Opening shot']],
            duration: 30,
            ratio: '9:16',
        );
    }

    public function test_it_generates_a_single_base_clip_when_duration_fits(): void
    {
        Storage::fake('public');
        config([
            'ai.providers.openai.key' => 'test-key',
            'ai.providers.openai.url' => 'https://api.openai.com/v1',
            'creative.openai_sora.model' => 'sora-2',
            'creative.openai_sora.poll_interval_seconds' => 0,
            'creative.openai_sora.max_poll_attempts' => 3,
        ]);

        Http::fake([
            'api.openai.com/v1/videos' => Http::response(['id' => 'video_abc', 'status' => 'queued']),
            'api.openai.com/v1/videos/video_abc' => Http::response(['id' => 'video_abc', 'status' => 'completed']),
            'api.openai.com/v1/videos/video_abc/content' => Http::response('FAKEVIDEOBYTES', 200),
        ]);

        $result = (new OpenAiSoraVideoAvatarProvider)->render(
            new Avatar(['name' => 'Ava', 'gender' => 'female', 'accent' => 'American']),
            new Voice(['name' => 'Kore', 'tone' => 'warm']),
            $this->script(),
            [['scene' => 'Opening shot']],
            duration: 8,
            ratio: '16:9',
        );

        $this->assertSame('openai-sora', $result->meta['provider']);
        $this->assertSame('sora-2', $result->meta['model']);
        $this->assertSame(8, $result->meta['duration']);
        Storage::disk('public')->assertExists($result->assetPath);
        $this->assertSame('FAKEVIDEOBYTES', Storage::disk('public')->get($result->assetPath));
        Http::assertSent(fn ($request) => $request->url() === 'https://api.openai.com/v1/videos'
            && $request['model'] === 'sora-2'
            && $request['size'] === '1280x720'
            && $request['seconds'] === '8');
    }

    public function test_it_extends_the_base_clip_when_duration_exceeds_the_max_base_length(): void
    {
        Storage::fake('public');
        config([
            'ai.providers.openai.key' => 'test-key',
            'ai.providers.openai.url' => 'https://api.openai.com/v1',
            'creative.openai_sora.model' => 'sora-2',
            'creative.openai_sora.poll_interval_seconds' => 0,
            'creative.openai_sora.max_poll_attempts' => 3,
        ]);

        Http::fake([
            'api.openai.com/v1/videos' => Http::response(['id' => 'video_base', 'status' => 'queued']),
            'api.openai.com/v1/videos/video_base' => Http::response(['id' => 'video_base', 'status' => 'completed']),
            'api.openai.com/v1/videos/extensions' => Http::response(['id' => 'video_ext', 'status' => 'queued']),
            'api.openai.com/v1/videos/video_ext' => Http::response(['id' => 'video_ext', 'status' => 'completed']),
            'api.openai.com/v1/videos/video_ext/content' => Http::response('FAKEVIDEOBYTES', 200),
        ]);

        $result = (new OpenAiSoraVideoAvatarProvider)->render(
            new Avatar(['name' => 'Ava']),
            new Voice(['name' => 'Kore']),
            $this->script(),
            [['scene' => 'Opening shot']],
            duration: 30,
            ratio: '9:16',
        );

        // base 12s (max supported) + smallest extension that covers the 18s remainder (20, since
        // 16 would undershoot the requested 30s) = 32s.
        $this->assertSame(32, $result->meta['duration']);
        $this->assertSame('video_ext', $result->meta['video_id']);
        Storage::disk('public')->assertExists($result->assetPath);

        Http::assertSent(fn ($request) => $request->url() === 'https://api.openai.com/v1/videos'
            && $request['seconds'] === '12');
        Http::assertSent(fn ($request) => $request->url() === 'https://api.openai.com/v1/videos/extensions'
            && $request['video']['id'] === 'video_base'
            && $request['seconds'] === '20');
    }

    public function test_it_throws_when_polling_times_out(): void
    {
        config([
            'ai.providers.openai.key' => 'test-key',
            'ai.providers.openai.url' => 'https://api.openai.com/v1',
            'creative.openai_sora.poll_interval_seconds' => 0,
            'creative.openai_sora.max_poll_attempts' => 2,
        ]);

        Http::fake([
            'api.openai.com/v1/videos' => Http::response(['id' => 'video_abc', 'status' => 'queued']),
            'api.openai.com/v1/videos/video_abc' => Http::response(['id' => 'video_abc', 'status' => 'in_progress']),
        ]);

        $this->expectException(RuntimeException::class);
        $this->expectExceptionMessage('Sora video generation timed out');

        (new OpenAiSoraVideoAvatarProvider)->render(
            new Avatar(['name' => 'Ava']),
            new Voice(['name' => 'Kore']),
            $this->script(),
            [['scene' => 'Opening shot']],
            duration: 15,
            ratio: '16:9',
        );
    }

    public function test_it_retries_on_a_transient_404_before_the_video_id_becomes_queryable(): void
    {
        Storage::fake('public');
        config([
            'ai.providers.openai.key' => 'test-key',
            'ai.providers.openai.url' => 'https://api.openai.com/v1',
            'creative.openai_sora.model' => 'sora-2',
            'creative.openai_sora.poll_interval_seconds' => 0,
            'creative.openai_sora.max_poll_attempts' => 3,
        ]);

        Http::fake([
            'api.openai.com/v1/videos' => Http::response(['id' => 'video_abc', 'status' => 'queued']),
            // Eventual-consistency lag: OpenAI briefly 404s the freshly created video id, then it becomes queryable.
            'api.openai.com/v1/videos/video_abc' => Http::sequence()
                ->push(['error' => ['message' => "Video with id 'video_abc' not found."]], 404)
                ->push(['id' => 'video_abc', 'status' => 'completed']),
            'api.openai.com/v1/videos/video_abc/content' => Http::response('FAKEVIDEOBYTES', 200),
        ]);

        $result = (new OpenAiSoraVideoAvatarProvider)->render(
            new Avatar(['name' => 'Ava']),
            new Voice(['name' => 'Kore']),
            $this->script(),
            [['scene' => 'Opening shot']],
            duration: 8,
            ratio: '16:9',
        );

        $this->assertSame(8, $result->meta['duration']);
        Storage::disk('public')->assertExists($result->assetPath);
    }

    public function test_it_fails_fast_on_a_non_404_error_without_retrying(): void
    {
        config([
            'ai.providers.openai.key' => 'test-key',
            'ai.providers.openai.url' => 'https://api.openai.com/v1',
            'creative.openai_sora.poll_interval_seconds' => 0,
            'creative.openai_sora.max_poll_attempts' => 5,
        ]);

        Http::fake([
            'api.openai.com/v1/videos' => Http::response(['id' => 'video_abc', 'status' => 'queued']),
            'api.openai.com/v1/videos/video_abc' => Http::response(['error' => ['message' => 'Incorrect API key provided.']], 401),
        ]);

        $this->expectException(RuntimeException::class);
        $this->expectExceptionMessage('Sora video polling failed');

        (new OpenAiSoraVideoAvatarProvider)->render(
            new Avatar(['name' => 'Ava']),
            new Voice(['name' => 'Kore']),
            $this->script(),
            [['scene' => 'Opening shot']],
            duration: 8,
            ratio: '16:9',
        );

        Http::assertSentCount(2);
    }
}
