<?php

namespace Tests\Feature;

use App\Agent\Choices;
use App\Agent\ToolRegistry;
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 Illuminate\Support\Facades\Storage;
use Laravel\Ai\Tools\Request;
use Tests\TestCase;

/**
 * Reaching creatives made outside the chat.
 *
 * The generator runs on its own screen, so its output belongs to a user but to
 * no conversation. Without this the chat asks for an upload while a finished
 * creative sits one screen away.
 */
class ImportCreativesTest extends TestCase
{
    use RefreshDatabase;

    protected User $user;

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

        Storage::fake('public');

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

        $workspace = app(Workspace::class);
        $workspace->bindTo('conv-import-0001');
        $workspace->ownedBy($this->user);
    }

    private function runTool(string $name, array $arguments = []): string
    {
        $tool = collect(app(ToolRegistry::class)->resolve())
            ->first(fn ($t) => $t->name() === $name);

        $this->assertNotNull($tool, "Tool {$name} is not registered.");

        return (string) $tool->handle(new Request($arguments));
    }

    private function finishedCreative(User $owner, string $path, array $attributes = []): Creative
    {
        Storage::disk('public')->put($path, 'bytes-'.$path);

        $creative = Creative::create(array_merge([
            'user_id' => $owner->id,
            'type' => 'image',
            'status' => 'generating',
            'source' => 'prompt',
        ], $attributes));

        $creative->update(['status' => 'completed', 'asset_path' => $path]);

        return $creative->fresh();
    }

    /** Offered as thumbnails now, rather than listed by id in prose. */
    public function test_calling_with_no_ids_offers_them_without_importing(): void
    {
        $creative = $this->finishedCreative($this->user, 'creatives/images/a.png', ['ratio' => '1:1']);

        $this->runTool('campaign__import_creatives');

        $offered = collect(app(Choices::class)->all())->firstWhere('field', 'creative_ids');

        $this->assertNotNull($offered, 'Creatives should be offered.');
        $this->assertTrue($offered['multiple'], 'More than one creative can be used.');
        $this->assertSame([(string) $creative->id], array_column($offered['options'], 'value'));

        // A creative is chosen by looking at it, so the preview has to be there.
        $this->assertStringContainsString(
            '/assets/'.$creative->asset_id.'/preview',
            (string) $offered['options'][0]['image'],
        );

        // Offering imports nothing.
        $this->assertCount(0, app(Workspace::class)->artifacts(Asset::class));
    }

    public function test_importing_puts_the_asset_in_the_conversation(): void
    {
        $creative = $this->finishedCreative($this->user, 'creatives/images/b.png');

        $result = $this->runTool('campaign__import_creatives', ['creative_ids' => [$creative->id]]);

        $this->assertStringContainsString('Imported 1', $result);

        $imported = app(Workspace::class)->artifacts(Asset::class)
            ->map(fn ($link) => $link->artifactable)->filter();

        $this->assertCount(1, $imported);
        $this->assertSame($creative->asset_id, $imported->first()->id);

        $offered = collect(app(Choices::class)->all())->firstWhere('field', 'copy_emphasis');
        $this->assertNotNull($offered, 'Copy emphasis choices should be offered.');
        $this->assertSame('What message or offer should the ad copy emphasize?', $offered['question']);
        $this->assertCount(3, $offered['options']);
        $this->assertTrue($offered['allow_other']);
    }

    /**
     * Importing must not move focus off the campaign.
     *
     * produced() focuses whatever it registers, which is right when the agent
     * creates something and wrong here: the creatives are being brought in for
     * a campaign that is already in focus. Focus landed on the last creative,
     * and the next tool reported no campaign to attach it to. Seen in a real
     * thread as "I briefly lost campaign focus when reviewing assets".
     */
    public function test_importing_leaves_the_campaign_in_focus(): void
    {
        $campaign = Campaign::create([
            'user_id' => $this->user->id,
            'name' => 'Winter Sale',
            'platform' => 'meta',
            'status' => 'draft',
        ]);

        $workspace = app(Workspace::class);
        $workspace->produced($campaign);

        $first = $this->finishedCreative($this->user, 'creatives/images/one.png');
        $second = $this->finishedCreative($this->user, 'creatives/images/two.png');

        $this->runTool('campaign__import_creatives', ['creative_ids' => [$first->id, $second->id]]);

        $focus = $workspace->focus();

        $this->assertInstanceOf(Campaign::class, $focus, 'The campaign should still be in focus after importing.');
        $this->assertSame($campaign->id, $focus->id);

        // And the creatives are still attachable, which is the point of importing.
        $this->assertCount(2, $workspace->artifacts(Asset::class));
    }

    // ------------------------------------------------- what the picker shows

    /**
     * Newest first is only right when nothing is known about the campaign.
     *
     * The campaign says what it is for, so a creative made for the same site
     * belongs at the top. Before this the picker was a reverse-chronological
     * dump and the right one could be anywhere in it.
     */
    public function test_a_creative_made_for_the_same_site_is_offered_first(): void
    {
        $this->finishedCreative($this->user, 'creatives/images/other.png', [
            'product_url' => 'https://someoneelse.com/thing',
        ]);
        $wanted = $this->finishedCreative($this->user, 'creatives/images/wanted.png', [
            'product_url' => 'https://stockprices.com/offer',
        ]);

        $campaign = Campaign::create([
            'user_id' => $this->user->id,
            'name' => 'Winter Sale',
            'platform' => 'meta',
            'status' => 'draft',
            'landing_url' => 'https://stockprices.com',
        ]);
        app(Workspace::class)->focusOn($campaign);

        $this->runTool('campaign__import_creatives');

        $offered = collect(app(Choices::class)->all())->firstWhere('field', 'creative_ids')['options'];

        $this->assertSame((string) $wanted->id, $offered[0]['value']);
        $this->assertSame('Suggested', $offered[0]['badge']);
    }

    /** A suggestion, not a filter: everything else is still reachable. */
    public function test_the_rest_are_still_offered(): void
    {
        $this->finishedCreative($this->user, 'creatives/images/a.png', ['product_url' => 'https://elsewhere.com']);
        $this->finishedCreative($this->user, 'creatives/images/b.png', ['product_url' => 'https://stockprices.com']);

        $campaign = Campaign::create([
            'user_id' => $this->user->id,
            'name' => 'Winter Sale',
            'platform' => 'meta',
            'status' => 'draft',
            'landing_url' => 'https://stockprices.com',
        ]);
        app(Workspace::class)->focusOn($campaign);

        $this->runTool('campaign__import_creatives');

        $this->assertCount(2, collect(app(Choices::class)->all())->firstWhere('field', 'creative_ids')['options']);
    }

    /** Format and ratio are what tell two thumbnails apart at a glance. */
    public function test_the_label_says_what_the_creative_is(): void
    {
        $this->finishedCreative($this->user, 'creatives/images/square.png', ['ratio' => '1:1']);

        $this->runTool('campaign__import_creatives');

        $offered = collect(app(Choices::class)->all())->firstWhere('field', 'creative_ids')['options'];

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

    public function test_the_label_uses_title_when_available(): void
    {
        $this->finishedCreative($this->user, 'creatives/images/square.png', [
            'title' => 'Rose Wood Performance Tee',
            'ratio' => '1:1',
        ]);

        $this->runTool('campaign__import_creatives');

        $offered = collect(app(Choices::class)->all())->firstWhere('field', 'creative_ids')['options'];

        $this->assertSame('Rose Wood Performance Tee', $offered[0]['label']);
        $this->assertStringContainsString('1:1 image', $offered[0]['hint']);
    }

    /**
     * Reusing a creative is normal, but doing it without knowing is not.
     *
     * A creative already carrying somebody's spend should say so before it is
     * put into a second campaign.
     */
    public function test_a_creative_already_in_a_campaign_says_so(): void
    {
        $creative = $this->finishedCreative($this->user, 'creatives/images/used.png');

        $campaign = Campaign::create([
            'user_id' => $this->user->id,
            'name' => 'Autumn Sale',
            'platform' => 'meta',
            'status' => 'published',
        ]);
        $campaign->ads()->create([
            'headline' => 'Autumn',
            'primary_text' => 'Half price.',
            'cta' => 'SHOP_NOW',
            'asset_id' => $creative->asset_id,
        ]);

        $this->runTool('campaign__import_creatives');

        $offered = collect(app(Choices::class)->all())->firstWhere('field', 'creative_ids')['options'];

        $this->assertStringContainsString('already in Autumn Sale', $offered[0]['hint']);
    }

    /**
     * With nothing to offer, say where to make one.
     *
     * The agent has no tool that generates a creative, so without the link it
     * either offers something it cannot do or dead-ends.
     */
    public function test_an_empty_library_points_at_the_generator(): void
    {
        $result = $this->runTool('campaign__import_creatives');

        $this->assertStringContainsString('creatives/create', $result);
        $this->assertStringContainsString('do not offer to generate one yourself', $result);
    }

    /**
     * A colleague's creative is not offered.
     *
     * The picker used to list everyone's, which reversed nothing in practice: the
     * creative library belongs to whoever generated it and always has, so the
     * shared listing was showing people work they had no relationship to rather
     * than enabling anything. Confirmed on 30 Sep - nobody was relying on it, and
     * there is no trade-off here to weigh.
     *
     * An earlier version of this docblock recorded one, hedging about a team where
     * one person generates and another launches. That team does not exist and the
     * hedge would have been read later as a reason to undo this.
     */
    public function test_another_users_creative_is_not_offered(): void
    {
        $stranger = User::factory()->create();
        $this->finishedCreative($stranger, 'creatives/images/theirs.png');

        $result = $this->runTool('campaign__import_creatives');

        $this->assertStringNotContainsString('theirs.png', $result);
    }

    /** And the user's own still is. */
    public function test_my_own_creative_is_still_offered(): void
    {
        $mine = $this->finishedCreative($this->user, 'creatives/images/mine.png');

        $result = $this->runTool('campaign__import_creatives', ['creative_ids' => [$mine->id]]);

        $this->assertStringContainsString('Imported 1', $result);
        $this->assertCount(1, app(Workspace::class)->artifacts(Asset::class));
    }

    public function test_a_creative_still_rendering_is_not_offered(): void
    {
        Creative::create([
            'user_id' => $this->user->id,
            'type' => 'video',
            'status' => 'processing',
            'source' => 'prompt',
        ]);

        $this->assertStringContainsString('No finished creatives', $this->runTool('campaign__import_creatives'));
    }
}
