<?php

namespace Tests\Feature;

use App\Agent\Tools\Campaign\AnalyzeLandingPage;
use App\Agent\Tools\Campaign\SetCampaignBudget;
use App\Agent\Tools\Campaign\StartCampaign;
use App\Agent\Workspace;
use App\Campaigns\Briefs;
use App\Models\Brief;
use App\Models\Campaign;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Laravel\Ai\Tools\Request;
use Tests\TestCase;

/**
 * A budget named for two platforms is their total, even in the first message.
 *
 * Seen on dev on 23 Sep. "Advertise <url> on Meta and Google. 20 a day total,
 * split it between them" created both campaigns, wrote the whole 20 to Meta,
 * which happened to be in focus, and left Google with nothing. The brief, which
 * is where a total belongs, held no budget at all, so split_budget would have
 * answered "ask for the total budget first".
 *
 * SetCampaignBudget decides between a total and one campaign's own by counting
 * the brief's campaigns. That count is right from the second message on, and
 * zero during the first: campaigns are created before the conversation exists,
 * so brief_id is null until Briefs::settle() back-fills it after the turn.
 *
 * Which makes this the shadow of the settle() fix. Attaching the brief late
 * repaired the brief itself and left anything that counted its campaigns mid
 * turn reading an empty set. Counted from the conversation now, which knows
 * both campaigns whether or not they have reached the database.
 */
class BudgetIsATotalOnTheFirstMessageTest extends TestCase
{
    use RefreshDatabase;

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

        Http::fake(['*' => Http::response(
            '<html><head><title>iBlockAds</title></head><body><h1>Block ads</h1>'
            .'<p>Browse faster on every site.</p></body></html>',
        )]);

        $user = User::factory()->create();
        $this->actingAs($user);
        app(Workspace::class)->ownedBy($user);
    }

    /**
     * The whole first turn, in the order the agent runs it.
     *
     * No bindTo() before the tools: that absence is the condition being tested.
     */
    private function firstTurn(array $platforms, float $amount): void
    {
        app(AnalyzeLandingPage::class)->handle(new Request(['url' => 'https://iblockads.org/']));

        foreach ($platforms as $platform) {
            app(StartCampaign::class)
                ->handle(new Request(['platform' => $platform]));
        }

        app(SetCampaignBudget::class)->handle(new Request(['amount' => $amount]));

        app(Workspace::class)->bindTo((string) Str::uuid());
        app(Briefs::class)->settle();
    }

    public function test_two_platforms_named_at_once_make_the_amount_a_total(): void
    {
        $this->firstTurn(['meta', 'google'], 20);

        $brief = Brief::query()->first();

        $this->assertNotNull($brief);
        $this->assertSame(
            '20.00',
            (string) $brief->budget,
            'The total went to one campaign instead of the brief, so the other platform has nothing.',
        );
    }

    /**
     * And no campaign holds the whole total as its own share.
     *
     * Asserted on the stored column, not on $campaign->budget: budget is one of
     * the fields ResolvesFromBrief falls back on, so a campaign with no share
     * of its own reports the brief's total. That is deliberate, and it is why
     * the check has to reach past the accessor to see what was actually
     * written. Reading the accessor, both campaigns say 20.00 whether the fix
     * is in place or not.
     */
    public function test_neither_campaign_takes_the_whole_total(): void
    {
        $this->firstTurn(['meta', 'google'], 20);

        foreach (Campaign::query()->get() as $campaign) {
            $this->assertNull(
                $campaign->getAttributes()['budget'] ?? null,
                "The {$campaign->platform} campaign stored the total as its own share.",
            );
        }
    }

    /**
     * One platform is unchanged: the amount is that campaign's own.
     *
     * The fix must not turn every budget into a total, or a single campaign
     * would be left at zero waiting for a split that has nothing to divide.
     */
    public function test_one_platform_still_sets_the_campaigns_own_budget(): void
    {
        $this->firstTurn(['meta'], 20);

        $this->assertSame('20.00', (string) Campaign::query()->sole()->budget);
        $this->assertNull(Brief::query()->first()?->budget);
    }

    /**
     * Two campaigns on the same platform are not a split.
     *
     * Distinct platforms, not campaign count: two Meta campaigns in one
     * conversation each have their own budget.
     */
    public function test_two_campaigns_on_one_platform_are_not_a_split(): void
    {
        $this->firstTurn(['meta', 'meta'], 20);

        $this->assertNull(
            Brief::query()->first()?->budget,
            'Two campaigns on the same platform were treated as a budget to divide.',
        );
    }
}
