<?php

namespace Tests\Unit\Services;

use App\Models\Avatar;
use App\Models\Voice;
use App\Services\AI\GeminiVeoVertexVideoProvider;
use App\Services\AI\VertexSafetyFilteredException;
use App\Support\VideoDialogue;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class GeminiVeoVertexVideoProviderTest extends TestCase
{
    public function test_it_builds_a_near_thirty_second_video_with_three_distinct_extensions(): void
    {
        Storage::fake('public');
        Cache::clear();

        $credentialsPath = $this->makeCredentialsFile();
        config([
            'services.vertex_ai.project_id' => 'test-project',
            'services.vertex_ai.credentials_path' => $credentialsPath,
            'services.vertex_ai.location' => 'us-central1',
            'creative.gemini_veo_vertex.model' => 'veo-test',
            'creative.gemini_veo_vertex.poll_interval_seconds' => 0,
            'creative.gemini_veo_vertex.max_poll_attempts' => 2,
            'creative.vertex_extend_durations' => [30 => 3],
            'creative.spoken_words_per_second' => 2,
        ]);

        $operation = 0;
        Http::fake(function (Request $request) use (&$operation) {
            if ($request->url() === 'https://oauth2.googleapis.com/token') {
                return Http::response(['access_token' => 'test-token']);
            }

            if (str_ends_with($request->url(), ':predictLongRunning')) {
                $operation++;

                return Http::response(['name' => "operations/{$operation}"]);
            }

            if (str_ends_with($request->url(), ':fetchPredictOperation')) {
                return Http::response([
                    'done' => true,
                    'response' => ['videos' => [[
                        'bytesBase64Encoded' => base64_encode("video-{$request['operationName']}"),
                        'mimeType' => 'video/mp4',
                    ]]],
                ]);
            }

            return Http::response([], 404);
        });

        $progress = [];
        $avatarImage = glob(public_path('assets/avatars/*.png'))[0];

        try {
            $result = (new GeminiVeoVertexVideoProvider)->render(
                new Avatar(['name' => 'Ava', 'gender' => 'female', 'accent' => 'American', 'source_path' => 'assets/avatars/'.basename($avatarImage)]),
                new Voice(['name' => 'Kore', 'tone' => 'warm']),
                [
                    'spoken_line' => implode(' ', array_fill(0, 50, 'word')),
                    'hook' => 'Markets move quickly every day.',
                    'problem' => 'Disconnected tools slow every decision.',
                    'product' => 'One dashboard unifies live market data.',
                    'benefits' => 'Track compare and act with confidence.',
                    'cta' => 'Start making faster decisions today.',
                ],
                [
                    ['scene' => 'Open on the presenter'],
                    ['scene' => 'Show the problem'],
                    ['scene' => 'Introduce the dashboard'],
                    ['scene' => 'Demonstrate the benefits'],
                    ['scene' => 'Finish with the call to action'],
                ],
                duration: 30,
                ratio: '9:16',
                style: 'Break-Out Type',
                onProgress: static function (int $percent, string $stage) use (&$progress): void {
                    $progress[] = [$percent, $stage];
                },
            );
        } finally {
            @unlink($credentialsPath);
        }

        $predictRequests = collect(Http::recorded())
            ->map(fn (array $record) => $record[0])
            ->filter(fn (Request $request) => str_ends_with($request->url(), ':predictLongRunning'))
            ->values();

        $this->assertCount(4, $predictRequests);
        $this->assertSame(8, $predictRequests[0]['parameters']['durationSeconds']);
        $this->assertSame(file_get_contents($avatarImage), base64_decode($predictRequests[0]['instances'][0]['image']['bytesBase64Encoded']));
        $this->assertArrayNotHasKey('image', $predictRequests[1]['instances'][0]);
        $this->assertArrayNotHasKey('durationSeconds', $predictRequests[1]['parameters']);
        $this->assertStringContainsString('Create only the opening 8 seconds', $predictRequests[0]['instances'][0]['prompt']);
        $this->assertStringContainsString('Continue seamlessly', $predictRequests[1]['instances'][0]['prompt']);
        $this->assertSame(29, $result->meta['duration']);
        $this->assertSame(3, $result->meta['extend_calls']);
        $this->assertArrayNotHasKey('prompt', $result->meta);
        $this->assertSame(64, strlen($result->meta['prompt_hash']));
        $this->assertArrayNotHasKey('dialogue_segments', $result->meta);
        $this->assertArrayNotHasKey('extend_prompts', $result->meta);
        $this->assertCount(3, array_unique($result->meta['extend_prompt_hashes']));
        foreach ([8, 7, 7, 7] as $index => $stageDuration) {
            preg_match('/- Presenter: "([^"]*)"/', $predictRequests[$index]['instances'][0]['prompt'], $matches);
            $this->assertLessThanOrEqual(VideoDialogue::wordBudget($stageDuration), VideoDialogue::wordCount($matches[1] ?? ''));
        }
        $this->assertSame(96, $progress[array_key_last($progress)][0]);
        $this->assertSame('Assembling final video', $progress[array_key_last($progress)][1]);
        $this->assertSame(
            collect($progress)->pluck(0)->sort()->values()->all(),
            collect($progress)->pluck(0)->values()->all(),
        );
        Storage::disk('public')->assertExists($result->assetPath);
    }

    public function test_it_retries_when_vertex_completes_without_video_bytes(): void
    {
        Storage::fake('public');
        Cache::clear();
        $credentialsPath = $this->makeCredentialsFile();
        config([
            'services.vertex_ai.project_id' => 'test-project',
            'services.vertex_ai.credentials_path' => $credentialsPath,
            'services.vertex_ai.location' => 'us-central1',
            'creative.gemini_veo_vertex.model' => 'veo-test',
            'creative.gemini_veo_vertex.poll_interval_seconds' => 0,
            'creative.gemini_veo_vertex.max_poll_attempts' => 2,
            'creative.vertex_extend_durations' => [15 => 1],
        ]);

        $operation = 0;
        Http::fake(function (Request $request) use (&$operation) {
            if ($request->url() === 'https://oauth2.googleapis.com/token') {
                return Http::response(['access_token' => 'test-token']);
            }

            if (str_ends_with($request->url(), ':predictLongRunning')) {
                $operation++;

                return Http::response(['name' => "operations/{$operation}"]);
            }

            if (str_ends_with($request->url(), ':fetchPredictOperation')) {
                if ($request['operationName'] === 'operations/1') {
                    return Http::response(['done' => true, 'response' => ['videos' => []]]);
                }

                return Http::response([
                    'done' => true,
                    'response' => ['videos' => [[
                        'bytesBase64Encoded' => base64_encode("video-{$request['operationName']}"),
                        'mimeType' => 'video/mp4',
                    ]]],
                ]);
            }

            return Http::response([], 404);
        });

        try {
            $result = (new GeminiVeoVertexVideoProvider)->render(
                new Avatar(['name' => 'Ava']),
                new Voice(['name' => 'Kore']),
                ['hook' => 'Start here.', 'problem' => 'Work is slow.', 'product' => 'Use Product.', 'benefits' => 'Move faster.', 'cta' => 'Try now.'],
                [['scene' => 'Presenter speaks'], ['scene' => 'Show product']],
                duration: 15,
                ratio: '9:16',
            );
        } finally {
            @unlink($credentialsPath);
        }

        $predictRequests = collect(Http::recorded())
            ->map(fn (array $record) => $record[0])
            ->filter(fn (Request $request) => str_ends_with($request->url(), ':predictLongRunning'));

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

    public function test_it_rephrases_a_safety_filtered_stage_with_neutral_visual_direction(): void
    {
        Storage::fake('public');
        Cache::clear();
        $credentialsPath = $this->makeCredentialsFile();
        config([
            'services.vertex_ai.project_id' => 'test-project',
            'services.vertex_ai.credentials_path' => $credentialsPath,
            'services.vertex_ai.location' => 'us-central1',
            'creative.gemini_veo_vertex.model' => 'veo-test',
            'creative.gemini_veo_vertex.poll_interval_seconds' => 0,
            'creative.gemini_veo_vertex.max_poll_attempts' => 2,
            'creative.vertex_extend_durations' => [15 => 1],
        ]);

        $operation = 0;
        Http::fake(function (Request $request) use (&$operation) {
            if ($request->url() === 'https://oauth2.googleapis.com/token') {
                return Http::response(['access_token' => 'test-token']);
            }

            if (str_ends_with($request->url(), ':predictLongRunning')) {
                $operation++;

                return Http::response(['name' => "operations/{$operation}"]);
            }

            if (str_ends_with($request->url(), ':fetchPredictOperation')) {
                if ($request['operationName'] === 'operations/1') {
                    return Http::response(['done' => true, 'response' => [
                        'videos' => [],
                        'raiMediaFilteredCount' => 1,
                        'raiMediaFilteredReasons' => ['Support codes: 15236754'],
                    ]]);
                }

                return Http::response(['done' => true, 'response' => ['videos' => [[
                    'bytesBase64Encoded' => base64_encode("video-{$request['operationName']}"),
                    'mimeType' => 'video/mp4',
                ]]]]);
            }

            return Http::response([], 404);
        });

        try {
            (new GeminiVeoVertexVideoProvider)->render(
                new Avatar(['name' => 'Ava']),
                new Voice(['name' => 'Kore']),
                ['hook' => 'Start here.', 'problem' => 'Work is slow.', 'product' => 'Use Product.', 'benefits' => 'Move faster.', 'cta' => 'Try now.'],
                [['scene' => 'Presenter speaks'], ['scene' => 'Show product']],
                duration: 15,
                ratio: '9:16',
                style: 'Kinetic Sport',
            );
        } finally {
            @unlink($credentialsPath);
        }

        $baseRequests = collect(Http::recorded())
            ->map(fn (array $record) => $record[0])
            ->filter(fn (Request $request) => str_ends_with($request->url(), ':predictLongRunning') && isset($request['parameters']['durationSeconds']))
            ->values();

        $this->assertCount(2, $baseRequests);
        $this->assertStringContainsString('Kinetic sports commercial', $baseRequests[0]['instances'][0]['prompt']);
        $this->assertStringContainsString('conservative neutral studio setting', $baseRequests[1]['instances'][0]['prompt']);
        $this->assertStringNotContainsString('Kinetic sports commercial', $baseRequests[1]['instances'][0]['prompt']);
    }

    public function test_repeated_safety_filtering_keeps_a_safe_vendor_reason(): void
    {
        Cache::clear();
        $credentialsPath = $this->makeCredentialsFile();
        config([
            'services.vertex_ai.project_id' => 'test-project',
            'services.vertex_ai.credentials_path' => $credentialsPath,
            'services.vertex_ai.location' => 'us-central1',
            'creative.gemini_veo_vertex.model' => 'veo-test',
            'creative.gemini_veo_vertex.poll_interval_seconds' => 0,
            'creative.gemini_veo_vertex.max_poll_attempts' => 2,
            'creative.vertex_extend_durations' => [15 => 1],
        ]);

        $operation = 0;
        Http::fake(function (Request $request) use (&$operation) {
            if ($request->url() === 'https://oauth2.googleapis.com/token') {
                return Http::response(['access_token' => 'test-token']);
            }

            if (str_ends_with($request->url(), ':predictLongRunning')) {
                return Http::response(['name' => 'operations/'.++$operation]);
            }

            if (str_ends_with($request->url(), ':fetchPredictOperation')) {
                return Http::response(['done' => true, 'response' => [
                    'videos' => [],
                    'raiMediaFilteredCount' => 1,
                    'raiMediaFilteredReasons' => ["A private prompt was filtered because it violated Vertex AI's usage guidelines. Support codes: 15236754"],
                ]]);
            }

            return Http::response([], 404);
        });

        try {
            (new GeminiVeoVertexVideoProvider)->render(
                new Avatar(['name' => 'Ava']),
                new Voice(['name' => 'Kore']),
                ['spoken_line' => 'Start here. Try now.'],
                [['scene' => 'Presenter speaks']],
                duration: 15,
                ratio: '9:16',
            );
            $this->fail('Expected the second safety filter to reject the render.');
        } catch (VertexSafetyFilteredException $exception) {
            $this->assertSame('Vertex AI usage guidelines (support code 15236754)', $exception->filterReason);
            $this->assertStringNotContainsString('private prompt', $exception->filterReason);
        } finally {
            @unlink($credentialsPath);
        }

        $this->assertSame(2, $operation);
    }

    private function makeCredentialsFile(): string
    {
        $key = openssl_pkey_new(['private_key_bits' => 2048]);
        openssl_pkey_export($key, $privateKey);
        $path = tempnam(sys_get_temp_dir(), 'vertex-test-');
        file_put_contents($path, json_encode([
            'client_email' => 'test@example.iam.gserviceaccount.com',
            'private_key' => $privateKey,
        ]));

        return $path;
    }
}
