<?php

namespace Tests\Feature;

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;

/**
 * Each platform gets copy written to its own limits.
 *
 * Meta takes a 40 character headline, Google 30, LinkedIn 70; the body is 125,
 * 90 and 600. One set of copy for the whole brief had to clear all of them at
 * once, so a three-platform brief was written to Google's 30 and 90 and the
 * other two were handed copy two thirds shorter than they would take. It
 * published, which is why nothing caught it, and it threw away most of the room
 * Meta and LinkedIn give you.
 *
 * Sharing one variation is still right for a single-platform brief, and for
 * copy that genuinely reads the same everywhere, so it stays the default rather
 * than being taken away.
 */
class CopyIsWrittenPerPlatformTest extends TestCase
{
    use RefreshDatabase;

    protected Brief $brief;

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

        config(['platforms.guardrails.max_ads_per_publish' => 10]);

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

        $workspace = app(Workspace::class);
        $workspace->bindTo('conv-per-platform-copy');

        $this->brief = Brief::create([
            'conversation_id' => 'conv-per-platform-copy',
            'user_id' => auth()->id(),
        ]);
    }

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

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

        return $campaign;
    }

    /** @param list<array<string,mixed>> $ads */
    private function write(array $ads): array
    {
        $result = collect(app(ToolRegistry::class)->resolve())
            ->first(fn ($t) => $t->name() === 'campaign__write_ad_copy')
            ->handle(new Request(['ads' => $ads]));

        return json_decode((string) $result, true);
    }

    /** @return list<array<string,mixed>> */
    private function threeAds(string $headline, ?string $platform = null, string $body = 'Body copy.'): array
    {
        return array_map(fn (int $i): array => array_filter([
            'platform' => $platform,
            'headline' => $headline.' '.$i,
            'primary_text' => $body,
            'cta' => 'LEARN_MORE',
        ]), [1, 2, 3]);
    }

    /**
     * The defect: a headline Meta accepts, refused because Google is also here.
     *
     * 36 characters is inside Meta's 40 and over Google's 30, and it used to be
     * refused outright for both.
     */
    public function test_meta_may_use_its_own_length_when_google_is_also_on_the_brief(): void
    {
        $meta = $this->campaign('meta');
        $google = $this->campaign('google');

        $result = $this->write([
            ...$this->threeAds('Block every annoying advert banner', 'meta'),
            ...$this->threeAds('Block ads now', 'google'),
        ]);

        $this->assertTrue($result['ok'], $result['error'] ?? '');
        $this->assertSame(3, $meta->ads()->count());
        $this->assertSame(3, $google->ads()->count());

        $this->assertStringContainsString('Block every annoying advert banner', $meta->ads()->first()->headline);
        $this->assertStringContainsString('Block ads now', $google->ads()->first()->headline);
    }

    /** A variation still cannot exceed its own platform's limit. */
    public function test_a_variation_over_its_own_platforms_limit_is_refused(): void
    {
        $this->campaign('meta');

        $result = $this->write($this->threeAds(str_repeat('a', 45), 'meta'));

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

    /** Shared copy still has to clear the tightest, which is the old behaviour. */
    public function test_shared_copy_is_still_held_to_the_tightest_limit(): void
    {
        $this->campaign('meta');
        $this->campaign('google');

        $result = $this->write($this->threeAds('Block every annoying advert banner'));

        $this->assertFalse($result['ok'], 'A 30-character-plus headline went to Google unchecked.');
    }

    /** Mixing is allowed: one platform tailored, the rest sharing. */
    public function test_one_platform_can_be_tailored_while_the_others_share(): void
    {
        $meta = $this->campaign('meta');
        $google = $this->campaign('google');

        $result = $this->write([
            ...$this->threeAds('Stop intrusive advert banners', 'meta'),
            ...$this->threeAds('Block ads now'),
        ]);

        $this->assertTrue($result['ok'], $result['error'] ?? '');
        $this->assertStringContainsString('Stop intrusive advert banners', $meta->ads()->first()->headline);
        $this->assertStringContainsString('Block ads now', $google->ads()->first()->headline);
    }

    /** Every platform's own room is reported, not one tightest number. */
    public function test_each_platforms_limits_come_back(): void
    {
        $this->campaign('meta');
        $this->campaign('google');

        $result = $this->write($this->threeAds('Block ads now'));

        $this->assertSame(40, $result['limits_by_platform']['meta']['headline']);
        $this->assertSame(30, $result['limits_by_platform']['google']['headline']);
    }

    /** A platform left without copy is refused rather than quietly skipped. */
    public function test_a_platform_with_no_copy_at_all_is_refused(): void
    {
        $this->campaign('meta');
        $this->campaign('google');

        $result = $this->write($this->threeAds('Block ads now', 'meta'));

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('No copy was written for', $result['error']);
    }

    /** A platform that is not in this conversation is refused. */
    public function test_copy_for_a_platform_that_is_not_here_is_refused(): void
    {
        $this->campaign('meta');

        $result = $this->write($this->threeAds('Block ads now', 'tiktok'));

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

    /** Google's three-variation floor is its own, not the brief's. */
    public function test_google_still_needs_three_variations_of_its_own(): void
    {
        $this->campaign('meta');
        $this->campaign('google');

        $result = $this->write([
            ...$this->threeAds('Block ads now', 'meta'),
            ['platform' => 'google', 'headline' => 'Block ads', 'primary_text' => 'Body.', 'cta' => 'LEARN_MORE'],
        ]);

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('Google Ads', $result['error']);
    }

    /**
     * Shared copy reports what it is leaving on the table.
     *
     * Telling the model it "could" tailor was not enough. On arb-dev on 25 Sep
     * it was asked outright for longer Meta headlines and moved to the next
     * step instead, leaving both platforms on 18 to 28 characters against
     * Meta's 40. A rule it has to remember loses to the step in front of it, so
     * the cost is measured and returned as a fact about this copy.
     */
    public function test_shared_copy_reports_the_room_it_leaves_unused(): void
    {
        $this->campaign('meta');
        $this->campaign('google');

        $result = $this->write($this->threeAds('Block ads now'));

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

        // Meta allows 40 where Google allows 30, so ten characters of every
        // shared headline are unavailable purely because one set serves both.
        $this->assertSame(10, $result['unused_room']['meta']['headline']);

        // Google set the constraint, so it loses nothing to it.
        $this->assertArrayNotHasKey('google', $result['unused_room']);
    }

    /**
     * It measures the constraint, not the writing.
     *
     * Long copy and short copy lose the same ten characters to sharing, so
     * both report the same thing. The first version compared the limit to the
     * longest line written and told Google it had twenty-five characters spare
     * on a fifteen character headline, which is true and recovers nothing.
     */
    public function test_the_room_reported_does_not_depend_on_how_long_the_copy_is(): void
    {
        $this->campaign('meta');
        $this->campaign('google');

        $short = $this->write($this->threeAds('Block ads'));
        $long = $this->write($this->threeAds('Block adverts for you now'));

        $this->assertSame(10, $short['unused_room']['meta']['headline']);
        $this->assertSame(10, $long['unused_room']['meta']['headline']);
    }

    /** One platform shares with nobody, so it loses nothing. */
    public function test_a_single_platform_brief_reports_no_lost_room(): void
    {
        $this->campaign('meta');

        $result = $this->write($this->threeAds('Block ads now'));

        $this->assertArrayNotHasKey('unused_room', $result);
    }

    /** Copy written per platform is already using its room. */
    public function test_tailored_copy_reports_no_unused_room(): void
    {
        $this->campaign('meta');
        $this->campaign('google');

        $result = $this->write([
            ...$this->threeAds('Block ads now', 'meta'),
            ...$this->threeAds('Block ads now', 'google'),
        ]);

        $this->assertArrayNotHasKey('unused_room', $result);
    }

    /** And the model is pointed at it rather than left to infer. */
    public function test_the_note_points_at_the_unused_room(): void
    {
        $this->campaign('meta');
        $this->campaign('google');

        $result = $this->write($this->threeAds('Block ads now'));

        $this->assertStringContainsString('unused_room', $result['note']);
    }
}
