<?php

namespace Tests\Feature;

use App\Agent\Answers;
use App\Agent\Choices;
use App\Agent\MarketingAgent;
use App\Agent\ToolRegistry;
use App\Agent\Workspace;
use App\Models\Brief;
use App\Models\Campaign;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Ai\Tools\Request;
use Tests\TestCase;

/**
 * One brief, a campaign on each platform chosen.
 *
 * "Promote this on Google and Meta" is the point of the whole rearrangement.
 * Until now a campaign was one platform, so advertising the same thing twice
 * meant answering the same fifteen questions twice.
 *
 * What these pin: several platforms can be picked at once, each gets its own
 * campaign, they share one brief so the shared answers are asked once, and the
 * first is the one in focus so the conversation has somewhere to point.
 */
class MultiPlatformStartTest extends TestCase
{
    use RefreshDatabase;

    protected User $user;

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

        MarketingAgent::fake(['Noted.']);

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

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

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

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

    public function test_picking_two_platforms_creates_a_campaign_for_each(): void
    {
        $result = $this->start(['platforms' => ['meta', 'google']]);

        $this->assertTrue($result['ok']);
        $this->assertCount(2, $result['campaigns']);
        $this->assertEqualsCanonicalizing(
            ['meta', 'google'],
            array_column($result['campaigns'], 'platform'),
        );
        $this->assertSame(2, Campaign::count());
    }

    /** The shared answers live in one place, or the second platform asks again. */
    public function test_both_campaigns_share_one_brief(): void
    {
        $this->start(['platforms' => ['meta', 'google']]);

        $briefs = Campaign::pluck('brief_id')->unique();

        $this->assertCount(1, $briefs);
        $this->assertNotNull($briefs->first());
        $this->assertSame(1, Brief::count());
    }

    /** The conversation has to point somewhere, and it points at the first. */
    public function test_the_first_platform_is_the_one_in_focus(): void
    {
        $result = $this->start(['platforms' => ['meta', 'google']]);

        $focus = app(Workspace::class)->focus();

        $this->assertInstanceOf(Campaign::class, $focus);
        $this->assertSame($result['campaign_id'], $focus->id);
        $this->assertSame('meta', $focus->platform);
    }

    /** One platform still behaves exactly as it did. */
    public function test_a_single_platform_still_works(): void
    {
        $result = $this->start(['platforms' => ['meta']]);

        $this->assertSame(1, Campaign::count());
        $this->assertSame('meta', $result['platform']);
        $this->assertArrayNotHasKey('note', $result, 'Nothing to explain when there is only one.');
    }

    /** The older singular argument has to keep working for anything still using it. */
    public function test_the_singular_platform_argument_still_works(): void
    {
        $this->assertTrue($this->start(['platform' => 'meta'])['ok']);
        $this->assertSame('meta', Campaign::first()->platform);
    }

    public function test_the_same_platform_twice_is_one_campaign(): void
    {
        $this->start(['platforms' => ['meta', 'meta']]);

        $this->assertSame(1, Campaign::count());
    }

    public function test_an_unsupported_platform_is_refused_and_creates_nothing(): void
    {
        $result = $this->start(['platforms' => ['meta', 'myspace']]);

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('myspace', $result['error']);
        $this->assertSame(0, Campaign::count(), 'A refused list must not half-create.');
    }

    /** Asked as one question with several answers, not one platform at a time. */
    public function test_calling_with_nothing_offers_every_platform_as_multi_select(): void
    {
        $this->start();

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

        $this->assertNotNull($offered, 'The platforms were not offered.');
        $this->assertTrue($offered['multiple'], 'More than one platform has to be pickable.');
        $this->assertContains('meta', array_column($offered['options'], 'value'));
        $this->assertContains('google', array_column($offered['options'], 'value'));
    }

    /** Clicking the options applies them here, rather than via the model. */
    public function test_clicked_platforms_apply_server_side(): void
    {
        app(Answers::class)->applyAll([
            ['field' => 'platforms', 'value' => ['meta', 'google']],
        ]);

        $this->assertSame(2, Campaign::count());
        $this->assertEqualsCanonicalizing(['meta', 'google'], Campaign::pluck('platform')->all());
    }

    /**
     * The name comes from the page when we have read one.
     *
     * A timestamp tells nobody anything, and the platform is appended so two
     * campaigns from one brief are told apart in a list.
     */
    public function test_names_are_drawn_from_the_landing_page_and_name_their_platform(): void
    {
        Brief::create([
            'user_id' => $this->user->id,
            'conversation_id' => 'conv-multi-0001',
            'landing_url' => 'https://example.com/shoes',
            'page_analysis' => [
                'url' => 'https://example.com/shoes',
                'title' => 'Acme Running Shoes',
                'description' => '',
                'headings' => ['Acme Running Shoes'],
                'text' => '',
                'keywords' => [],
            ],
        ]);

        $this->start(['platforms' => ['meta', 'google']]);

        $names = Campaign::pluck('name')->all();

        $this->assertStringStartsWith('acme-running-shoes_', $names[0]);
        $this->assertStringContainsString('_META_', $names[0]);
        $this->assertStringStartsWith('acme-running-shoes_', $names[1]);
        $this->assertStringContainsString('_GOOGLE_', $names[1]);
        $this->assertNotSame($names[0], $names[1], 'Two campaigns from one brief must be told apart.');
    }

    /**
     * A second, unrelated campaign later in the same conversation is its own.
     *
     * It must not join the first brief: its budget would then be read as a
     * share of that brief's total, and two campaigns with nothing to do with
     * each other would be spending the same money.
     */
    public function test_a_separate_campaign_started_later_does_not_join_the_brief(): void
    {
        $this->start(['platforms' => ['meta', 'google']]);
        $shared = Campaign::pluck('brief_id')->unique()->first();

        $this->start(['platforms' => ['tiktok'], 'name' => 'Something else entirely']);

        $later = Campaign::where('platform', 'tiktok')->first();

        $this->assertNotSame($shared, $later->brief_id);
        $this->assertNull($later->brief_id);
    }

    /** Informational questions about platforms do not trigger campaign creation. */
    public function test_informational_query_about_platforms_does_not_trigger_multi_platform_stepper(): void
    {
        $this->postJson(route('chat.send'), [
            'message' => 'What platforms do you support?',
        ])->assertOk();

        $this->assertSame(0, Campaign::count(), 'Informational question must not create a draft campaign.');
    }
}
