<?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\Campaign;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Ai\Tools\Request;
use Tests\TestCase;

/**
 * The button on the ad, offered from what the platform actually has.
 *
 * It used to arrive as free text beside the copy and be checked against one
 * hardcoded list of seven - Meta's - whatever the campaign ran on. LinkedIn was
 * therefore allowed BOOK_TRAVEL, which it has no button for, and refused APPLY,
 * REGISTER, REQUEST_DEMO and VIEW_QUOTE, four of its own ten. The failure landed
 * at publish as an enum error, after the copy had been agreed.
 *
 * What these pin: the list is the campaign's platform's, a button that platform
 * does not have is refused, a platform with no buttons says so instead of
 * offering seven, and the pick lands on every ad rather than one.
 */
class ChooseCallToActionTest 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-cta-0001');
        $workspace->ownedBy($this->user);
    }

    private function campaign(string $platform, int $ads = 3): Campaign
    {
        $campaign = Campaign::create([
            'user_id' => $this->user->id,
            'name' => ucfirst($platform).' campaign',
            'platform' => $platform,
            'status' => 'draft',
        ]);

        for ($i = 1; $i <= $ads; $i++) {
            $campaign->ads()->create([
                'headline' => "Headline {$i}",
                'primary_text' => "Body {$i}.",
                'cta' => 'LEARN_MORE',
            ]);
        }

        app(Workspace::class)->focusOn($campaign->fresh());

        return $campaign->fresh();
    }

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

        $this->assertNotNull($tool, 'choose_call_to_action is not registered.');

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

    private function offered(): ?array
    {
        return collect(app(Choices::class)->all())->firstWhere('field', 'cta');
    }

    public function test_the_buttons_offered_are_the_platforms_own(): void
    {
        $this->campaign('meta');

        $this->assertTrue($this->runTool()['ok']);

        $values = array_column($this->offered()['options'], 'value');

        $this->assertContains('SHOP_NOW', $values);
        $this->assertContains('BOOK_TRAVEL', $values);
        $this->assertNotContains('REQUEST_DEMO', $values, 'REQUEST_DEMO is LinkedIn\'s, not Meta\'s.');
    }

    /** The reversal of the same question, which is the half that was broken. */
    public function test_linkedin_is_offered_linkedins_buttons(): void
    {
        $this->campaign('linkedin');

        $this->runTool();

        $values = array_column($this->offered()['options'], 'value');

        $this->assertContains('REQUEST_DEMO', $values);
        $this->assertContains('APPLY', $values);
        $this->assertNotContains('BOOK_TRAVEL', $values, 'LinkedIn has no Book Now button.');
    }

    /** A Google Search ad has no button, so there is nothing to offer. */
    public function test_a_platform_with_no_buttons_says_so_rather_than_offering_metas(): void
    {
        $this->campaign('google');

        $result = $this->runTool();

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('no call to action', $result['error']);
        $this->assertNull($this->offered(), 'Nothing should be put on screen.');
    }

    public function test_an_unsupported_button_is_refused_with_the_supported_list(): void
    {
        $this->campaign('linkedin');

        $result = $this->runTool(['cta' => 'BOOK_TRAVEL']);

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('linkedin', $result['error']);
        $this->assertContains('REQUEST_DEMO', $result['supported']);
    }

    /**
     * The button describes the offer, not the variation.
     *
     * Three ads reading Shop Now, Learn More and Sign Up is not a test anybody
     * asked for, and the buyer who picks one means it for the campaign.
     */
    public function test_the_choice_lands_on_every_ad(): void
    {
        $campaign = $this->campaign('meta');

        $this->assertTrue($this->runTool(['cta' => 'SHOP_NOW'])['ok']);

        $this->assertSame(
            ['SHOP_NOW', 'SHOP_NOW', 'SHOP_NOW'],
            $campaign->ads()->pluck('cta')->all(),
        );
    }

    /** Clicking the button applies it here, not through the model. */
    public function test_the_clicked_button_applies_server_side(): void
    {
        $campaign = $this->campaign('meta');

        app(Answers::class)->applyAll([['field' => 'cta', 'value' => 'SIGN_UP']]);

        $this->assertSame('SIGN_UP', $campaign->ads()->value('cta'));
    }

    /** Offering is not applying. */
    public function test_offering_changes_nothing(): void
    {
        $campaign = $this->campaign('meta');

        $this->runTool();

        $this->assertSame('LEARN_MORE', $campaign->ads()->value('cta'));
    }

    /** What the ads carry now is marked, so the ordinary case stays one click. */
    public function test_the_current_button_is_marked(): void
    {
        $this->campaign('meta');

        $this->runTool();

        $current = collect($this->offered()['options'])->firstWhere('value', 'LEARN_MORE');

        $this->assertSame('Current', $current['badge'] ?? null);
    }

    /** There is nothing to set a button on before the copy exists. */
    public function test_it_asks_for_the_copy_first_when_there_are_no_ads(): void
    {
        $this->campaign('meta', ads: 0);

        $result = $this->runTool();

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('ad copy first', $result['error']);
    }
}
