<?php

namespace Tests\Feature;

use App\Assets\Thumbnails;
use App\Models\Asset;
use App\Models\AssetPlatformRef;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

/**
 * A video creative shows the still the platform made for it.
 *
 * Thumbnails::for() returned null for anything that was not an image, so the
 * preview route served the video's own bytes. The panel renders a creative in
 * an <img>, so an ad carrying a video showed an empty box, and downloaded the
 * whole file to show it.
 *
 * Meta already produces a still when it transcodes an upload, and it is the
 * frame the ad itself uses, so it is the right one to show rather than a frame
 * of our own choosing.
 */
class VideoThumbnailTest extends TestCase
{
    use RefreshDatabase;

    private function video(?string $externalId = 'vid_1'): Asset
    {
        $asset = Asset::create([
            'owner_type' => User::class,
            'owner_id' => User::factory()->create()->id,
            'kind' => 'video',
            'disk' => 'local',
            'path' => 'videos/a.mp4',
            'mime' => 'video/mp4',
        ]);

        if ($externalId !== null) {
            AssetPlatformRef::create([
                'asset_id' => $asset->id,
                'platform' => 'meta',
                'ad_account_id' => 'act_777',
                'external_id' => $externalId,
            ]);
        }

        return $asset->fresh();
    }

    private function fakeMeta(): void
    {
        Http::fake([
            '*/thumbnails*' => Http::response(['data' => [
                ['uri' => 'https://scontent.example/still.jpg', 'is_preferred' => true],
            ]]),
            'https://scontent.example/*' => Http::response('JPEGBYTES', 200),
            '*' => Http::response([], 200),
        ]);
    }

    public function test_an_uploaded_video_gets_the_platforms_still(): void
    {
        Storage::fake('local');
        $this->fakeMeta();

        $result = app(Thumbnails::class)->for($this->video());

        $this->assertNotNull($result, 'A video still has no thumbnail.');
        [$disk, $path] = $result;

        $this->assertSame('JPEGBYTES', Storage::disk($disk)->get($path));
    }

    /** Fetched once. The panel renders every ad on every turn. */
    public function test_the_still_is_fetched_once_and_kept(): void
    {
        Storage::fake('local');
        $this->fakeMeta();

        $asset = $this->video();
        $thumbnails = app(Thumbnails::class);

        $thumbnails->for($asset);
        $before = count(Http::recorded());

        $thumbnails->for($asset);

        $this->assertSame($before, count(Http::recorded()), 'The still was fetched again.');
    }

    /**
     * A video that has not been uploaded yet has no still to ask for.
     *
     * Null rather than an error: the caller falls back to serving the original,
     * which is what happened for every video before this existed.
     */
    public function test_a_video_that_is_not_uploaded_yet_has_no_still(): void
    {
        Storage::fake('local');
        $this->fakeMeta();

        $this->assertNull(app(Thumbnails::class)->for($this->video(externalId: null)));
    }

    /** And the panel knows not to render an <img> for it. */
    public function test_only_an_uploaded_video_reports_a_still(): void
    {
        $this->assertTrue($this->video()->hasStill());
        $this->assertFalse($this->video(externalId: null)->hasStill());
    }

    /**
     * The preview route serves the still even when our copy of the video is gone.
     *
     * Found on screen, not in a test: the panel rendered the <img> and the play
     * badge and the image never loaded. The controller aborted 404 on the
     * source file existing, checked first and unconditionally, before it ever
     * consulted the thumbnail. A video's still comes from the platform rather
     * than from our file, so the guard was refusing to serve something it
     * already had.
     */
    public function test_the_preview_serves_a_still_even_when_the_source_is_missing(): void
    {
        Storage::fake('local');
        $this->fakeMeta();

        $asset = $this->video();

        // Deliberately no file written at $asset->path.
        $this->actingAs(User::find($asset->owner_id))
            ->get(route('assets.preview', $asset))
            ->assertOk()
            ->assertHeader('Content-Type', 'image/jpeg');
    }

    /** An image with no file and no thumbnail is still a 404, which is what makes the above a fix and not a hole. */
    public function test_an_image_with_no_file_is_still_not_found(): void
    {
        Storage::fake('local');

        $user = User::factory()->create();
        $asset = Asset::create([
            'owner_type' => User::class,
            'owner_id' => $user->id,
            'kind' => 'image',
            'disk' => 'local',
            'path' => 'images/missing.png',
            'mime' => 'image/png',
        ]);

        $this->actingAs($user)
            ->get(route('assets.preview', $asset))
            ->assertNotFound();
    }

    /** A failure to fetch must not take the panel down with it. */
    public function test_a_thumbnail_that_cannot_be_fetched_falls_back_quietly(): void
    {
        Storage::fake('local');

        Http::fake([
            '*/thumbnails*' => Http::response(['data' => [
                ['uri' => 'https://scontent.example/still.jpg', 'is_preferred' => true],
            ]]),
            'https://scontent.example/*' => Http::response('', 500),
            '*' => Http::response([], 200),
        ]);

        $this->assertNull(app(Thumbnails::class)->for($this->video()));
    }
}
