<?php

namespace Tests\Feature;

use App\Agent\Tools\Campaign\WriteAdCopy;
use App\Agent\Workspace;
use App\Campaigns\LaunchFlow;
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;

/**
 * Ad copy is written for every campaign on the brief, and nothing claims a
 * conversation is ready while one of them is empty.
 *
 * From dev on 23 Sep, a Meta and Google thread. Every "write the ad copy", "fix
 * primary text" and "create 2 more headlines" landed on Google, because Google
 * was in focus, and 25 of the 27 campaign tools act on the focus rather than
 * taking an id. The Meta campaign finished with zero ads and the chat said
 * "Everything is ready. Shall I publish this campaign?" - true of the tab on
 * screen, and not of the campaign behind it.
 *
 * Two halves. Copy now goes to every campaign sharing the brief, which is the
 * same rule the landing page, budget and dates already follow. And the ready
 * sentence reports the whole brief rather than the focus.
 *
 * Copy cannot simply be duplicated, because the platforms do not accept the
 * same text: Meta allows a 40 character headline and Google 30, Meta 125
 * characters of body and Google 90. So a brief covering both is held to the
 * tightest, and over-long copy is refused rather than trimmed, because trimming
 * produces copy nobody wrote and nobody approved.
 */
class AdCopyCoversEveryCampaignTest extends TestCase
{
    use RefreshDatabase;

    private User $user;

    private Brief $brief;

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

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

        $this->brief = Brief::create([
            'user_id' => $this->user->id,
            'conversation_id' => 'conv-copy',
            'landing_url' => 'https://iblockads.org/',
        ]);

        app(Workspace::class)->ownedBy($this->user);
        app(Workspace::class)->bindTo('conv-copy');
    }

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

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

        return $campaign;
    }

    /** @return array<string,mixed> */
    private function write(array $ads): array
    {
        return json_decode((string) app(WriteAdCopy::class)->handle(new Request(['ads' => $ads])), true);
    }

    /**
     * Three variations, because Google will not take fewer.
     *
     * Its responsive search ad is built from the headlines of every ad on the
     * campaign, so a brief covering Google needs three different ones.
     */
    private function threeAds(string $headline = 'Block annoying ads'): array
    {
        return [
            [
                'headline' => $headline,
                'primary_text' => 'Browse faster on every site.',
                'description' => 'Free browser extension.',
                'cta' => 'LEARN_MORE',
            ],
            [
                'headline' => 'Browse faster today',
                'primary_text' => 'Stop trackers and pop ups.',
                'description' => 'Free browser extension.',
                'cta' => 'LEARN_MORE',
            ],
            [
                'headline' => 'Block pop ups',
                'primary_text' => 'A cleaner internet, everywhere.',
                'description' => 'Free browser extension.',
                'cta' => 'LEARN_MORE',
            ],
        ];
    }

    /** One, for the cases that are Meta only. */
    private function oneAd(string $headline = 'Block annoying ads'): array
    {
        return [$this->threeAds($headline)[0]];
    }

    public function test_copy_written_once_reaches_both_platforms(): void
    {
        $meta = $this->campaign('meta');
        $google = $this->campaign('google');

        // Google was produced last, so it is what the buyer is looking at.
        $this->assertTrue(app(Workspace::class)->focus()->is($google));

        $result = $this->write($this->threeAds());

        $this->assertTrue($result['ok'], $result['error'] ?? '');
        $this->assertSame(3, $meta->ads()->count(), 'The campaign off screen got no ads at all.');
        $this->assertSame(3, $google->ads()->count());
    }

    /** A campaign on its own keeps its own copy. */
    public function test_a_campaign_with_no_brief_is_written_alone(): void
    {
        $alone = Campaign::create([
            'user_id' => $this->user->id,
            'name' => 'Standalone',
            'platform' => 'meta',
            'status' => 'draft',
        ]);

        app(Workspace::class)->produced($alone);

        $this->assertTrue($this->write($this->oneAd())['ok']);
        $this->assertSame(1, $alone->ads()->count());
    }

    /**
     * A live campaign is left alone.
     *
     * Its ads are on the platform. Replacing the rows here would put our record
     * out of step with what is running without changing anything on the
     * platform.
     */
    public function test_a_published_campaign_is_not_rewritten(): void
    {
        $live = $this->campaign('meta', ['status' => 'published', 'external_campaign_id' => '123']);
        $draft = $this->campaign('google');

        $this->write($this->threeAds());

        $this->assertSame(0, $live->ads()->count(), 'Copy was rewritten under a running campaign.');
        $this->assertSame(3, $draft->ads()->count());
    }

    /**
     * Held to the tightest platform, and refused rather than trimmed.
     *
     * 35 characters is fine for Meta and over Google's 30. Publishing would
     * have failed at the ad, after the campaign and its budget already existed.
     */
    public function test_copy_too_long_for_one_platform_is_refused_with_the_number(): void
    {
        $this->campaign('meta');
        $this->campaign('google');

        $result = $this->write($this->threeAds('Block every annoying ad you see now'));

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('35 characters', $result['error']);
        $this->assertStringContainsString('limit here is 30', $result['error']);
        $this->assertSame(30, $result['limits']['headline']);
    }

    /** The same copy is accepted when only Meta is on the brief. */
    public function test_the_same_copy_is_fine_on_meta_alone(): void
    {
        $meta = $this->campaign('meta');

        $result = $this->write($this->oneAd('Block every annoying ad you see now'));

        $this->assertTrue($result['ok'], $result['error'] ?? '');

        // Reported per platform rather than as one tightest number, so a model
        // writing for Meta knows it has Meta's forty and not Google's thirty.
        $this->assertSame(40, $result['limits_by_platform']['meta']['headline']);
        $this->assertSame(1, $meta->ads()->count());
    }

    /** Nothing is written when one variation is refused. */
    public function test_a_refusal_writes_nothing_at_all(): void
    {
        $meta = $this->campaign('meta');
        $google = $this->campaign('google');

        $this->write([
            ...$this->threeAds(),
            ...$this->oneAd('Block every annoying ad you see now'),
        ]);

        $this->assertSame(0, $meta->ads()->count());
        $this->assertSame(0, $google->ads()->count());
    }

    /** The flow names what the other campaign is still waiting on. */
    public function test_the_flow_reports_the_campaign_off_screen(): void
    {
        $meta = $this->campaign('meta');
        $google = $this->campaign('google');

        $outstanding = app(LaunchFlow::class)->outstandingElsewhere($google);

        $this->assertArrayHasKey($meta->name, $outstanding);
        $this->assertNotEmpty($outstanding[$meta->name]);
    }

    /** And says nothing when there is nothing else. */
    public function test_a_single_campaign_has_nothing_outstanding_elsewhere(): void
    {
        $this->assertSame([], app(LaunchFlow::class)->outstandingElsewhere($this->campaign('meta')));
    }
}
