<?php

namespace Tests\Feature;

use App\Agent\Choices;
use App\Agent\Tools\Campaign\ImportCreatives;
use App\Agent\Workspace;
use App\Models\Asset;
use App\Models\Campaign;
use App\Models\Creative;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Ai\Tools\Request;
use Tests\TestCase;

/**
 * The creative picker says which creative each one is.
 *
 * Every image creative was labelled by its shape and nothing else, so four
 * square images read "1:1 Image" four times over and the only way to tell them
 * apart was the thumbnail. Shyam, 23 Sep, on seeing it: "there is no preview no
 * info nothing, how does one select a creative".
 *
 * The information existed the whole time. The generator writes a title for
 * every creative - "Women's Light-Wash Cuffed Straight-Leg Jeans" - and the
 * picker never read the column. The shape moved to the hint, where it still
 * answers where this can run without pretending to answer which one it is.
 */
class CreativePickerSaysWhichOneTest extends TestCase
{
    use RefreshDatabase;

    private User $user;

    private Campaign $campaign;

    protected function setUp(): void
    {
        parent::setUp();

        $this->user = User::factory()->create();
        $this->actingAs($this->user);

        $this->campaign = Campaign::create([
            'user_id' => $this->user->id,
            'name' => 'Jeans',
            'platform' => 'meta',
            'status' => 'draft',
        ]);

        app(Workspace::class)->ownedBy($this->user);
        app(Workspace::class)->produced($this->campaign);
    }

    private function creative(array $attributes): Creative
    {
        $asset = Asset::create([
            'owner_type' => $this->user->getMorphClass(),
            'owner_id' => $this->user->id,
            'kind' => 'image',
            'source' => 'generated',
            'path' => 'creatives/images/'.uniqid().'.jpg',
        ]);

        return Creative::create([
            'user_id' => $this->user->id,
            'type' => 'image',
            'status' => 'completed',
            'source' => 'generated',
            'asset_id' => $asset->id,
            ...$attributes,
        ]);
    }

    /** @return list<array<string,mixed>> */
    private function offered(): array
    {
        app(ImportCreatives::class)->handle(new Request([]));

        $offers = app(Choices::class)->all();

        $this->assertNotEmpty($offers, 'Nothing was offered to pick from.');

        return $offers[0]['options'];
    }

    public function test_the_label_is_the_creatives_title_not_its_shape(): void
    {
        $this->creative(['ratio' => '1:1', 'title' => "Women's Light-Wash Cuffed Straight-Leg Jeans"]);

        $option = $this->offered()[0];

        $this->assertSame(
            "Women's Light-Wash Cuffed Straight-Leg Jeans",
            $option['label'],
            'The picker still labels creatives by shape, which does not distinguish them.',
        );
    }

    /**
     * Two creatives of the same shape are told apart.
     *
     * This is the case that made it useless: a variation group is several
     * creatives of one size on purpose.
     */
    public function test_two_creatives_of_the_same_shape_have_different_labels(): void
    {
        $this->creative(['ratio' => '1:1', 'title' => 'Straight-Leg Jeans, studio shot']);
        $this->creative(['ratio' => '1:1', 'title' => 'Straight-Leg Jeans, worn outdoors']);

        $labels = array_column($this->offered(), 'label');

        $this->assertCount(2, array_unique($labels), 'Both creatives carry the same label: '.implode(' | ', $labels));
    }

    /** The shape is still on screen, as the thing it actually answers. */
    public function test_the_shape_moves_to_the_hint(): void
    {
        $this->creative(['ratio' => '9:16', 'title' => 'Jeans on the move', 'source' => 'product_url']);

        $option = $this->offered()[0];

        $this->assertStringContainsString('9:16', $option['hint']);
        $this->assertStringContainsString('from product_url', $option['hint']);
    }

    /** A creative with no title falls back to what there is. */
    public function test_an_untitled_creative_still_reads_as_something(): void
    {
        $this->creative(['ratio' => '4:5', 'title' => null]);

        $this->assertSame('4:5 image', $this->offered()[0]['label']);
    }

    /**
     * Long titles are trimmed rather than allowed to swallow the card.
     *
     * One generator prefixes the title with the product URL, which is how a
     * label comes to start "https://www.marshalls.com/-4:5-120 Hour ...".
     */
    public function test_a_long_title_is_trimmed(): void
    {
        $this->creative([
            'ratio' => '1:1',
            'title' => 'https://www.marshalls.com/-4:5-120 Hour Hybrid Active Noise Cancelling Over-Ear Headphones With Case',
        ]);

        $label = $this->offered()[0]['label'];

        $this->assertLessThanOrEqual(53, mb_strlen($label), "Label was not trimmed: {$label}");
        $this->assertStringEndsWith('…', $label);
    }

    /** The picture is still offered, which is what the cards layout keys on. */
    public function test_an_image_creative_still_carries_a_preview(): void
    {
        $this->creative(['ratio' => '1:1', 'title' => 'Jeans']);

        $this->assertNotNull(
            $this->offered()[0]['image'],
            'Without an image the renderer falls back to a text list and the previews vanish.',
        );
    }

    /**
     * A video gets something to show, because there is no poster frame.
     *
     * thumbnail_path is empty on every video in the library, so nothing can be
     * rendered for them. On dev that was 10 blank cards sitting among 17 with
     * pictures, which reads as the picker being broken rather than as videos
     * simply not having a still.
     */
    public function test_a_video_gets_an_icon_since_it_has_no_poster_frame(): void
    {
        $creative = $this->creative(['ratio' => '9:16', 'title' => 'Jeans in motion', 'duration' => 12]);
        $creative->update(['type' => 'video']);
        $creative->asset->update(['kind' => 'video']);

        $option = $this->offered()[0];

        $this->assertNull($option['image'], 'There is no still to show for a video.');
        $this->assertSame('bi-film', $option['icon'], 'The card has nothing to render, so it renders blank.');
        $this->assertStringContainsString('12s', $option['hint']);
    }

    /** An image needs no stand-in, and must not get one over its own preview. */
    public function test_an_image_creative_carries_no_icon(): void
    {
        $this->creative(['ratio' => '1:1', 'title' => 'Jeans']);

        $this->assertNull($this->offered()[0]['icon']);
    }

    /**
     * Titles built from the ratio and type lose them again.
     *
     * Everything generated before 23 Sep is titled
     * "{project}-{ratio}-{name}-{type}", so the label said 1:1 and Image while
     * the hint said the same, and the actual name was cut off past the
     * truncation. The generator was fixed; the library was not.
     */
    public function test_a_legacy_title_loses_the_ratio_and_type_it_repeats(): void
    {
        $this->creative([
            'ratio' => '1200x1200',
            'title' => 'Search.com-1200x1200-Browser Ad Campaign Qualified Traffic-Image',
        ]);

        $this->assertSame(
            'Browser Ad Campaign Qualified Traffic',
            $this->offered()[0]['label'],
        );
    }

    /**
     * A title that genuinely begins with a ratio keeps it.
     *
     * The strip matches this creative's own ratio and type, so it cannot eat
     * the name of a creative actually called "16:9 Launch Film".
     */
    public function test_a_real_title_that_mentions_a_shape_is_left_alone(): void
    {
        $this->creative(['ratio' => '1:1', 'title' => '16:9 Launch Film']);

        $this->assertSame('16:9 Launch Film', $this->offered()[0]['label']);
    }

    /**
     * A name cut mid-title does not keep the hyphen it was cut before.
     *
     * The old builder truncated the name to 30 characters before joining on
     * hyphens, so removing the type left "120Hr Hybrid ANC Escape-" on screen.
     */
    public function test_a_trailing_separator_is_not_left_behind(): void
    {
        $this->creative([
            'ratio' => '9:16',
            'title' => 'Headphones-9:16-120Hr Hybrid ANC Escape--Image',
        ]);

        $this->assertSame('120Hr Hybrid ANC Escape', $this->offered()[0]['label']);
    }

    /** A title already clean is untouched. */
    public function test_a_clean_title_passes_through(): void
    {
        $this->creative(['ratio' => '4:5', 'title' => 'Straight-Leg Jeans, worn outdoors']);

        $this->assertSame('Straight-Leg Jeans, worn outdoors', $this->offered()[0]['label']);
    }
}
