<?php

namespace Tests\Feature;

use App\Agent\ToolRegistry;
use App\Agent\Workspace;
use App\Campaigns\Platforms\GoogleRules;
use App\Campaigns\Platforms\LinkedInRules;
use App\Campaigns\Platforms\MetaRules;
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;

/**
 * The other two questions a brief was answering once per platform.
 *
 * Countries and bid strategy are both one decision for a brief, and both were
 * being asked again for every platform in it.
 *
 * Countries had the machinery and no data: locations has been in
 * SHARED_WITH_BRIEF since the trait was written and nothing ever wrote it, so
 * the fallback could not fire. Bidding had neither, and needed a mapping of its
 * own, because "bid automatically" is LOWEST_COST_WITHOUT_CAP on Meta,
 * MAXIMIZE_CLICKS on Google and AUTOMATED_BID on LinkedIn.
 */
class TargetingAndBiddingAreAskedOnceTest extends TestCase
{
    use RefreshDatabase;

    protected Brief $brief;

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

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

        app(Workspace::class)->bindTo('conv-shared-steps');

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

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

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

        return $campaign;
    }

    private function tool(string $name, array $arguments = []): array
    {
        $result = collect(app(ToolRegistry::class)->resolve())
            ->first(fn ($t) => $t->name() === $name)
            ->handle(new Request($arguments));

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

    // ---------------------------------------------------------------- countries

    /** The defect: the answer never left the campaign that gave it. */
    public function test_countries_reach_the_brief(): void
    {
        $this->campaign('meta');

        $this->tool('campaign__set_targeting', ['countries' => ['US', 'IN'], 'placements' => ['facebook']]);

        $this->assertSame(['US', 'IN'], $this->brief->refresh()->locations['countries'] ?? null);
    }

    /** So the next platform inherits them instead of being asked. */
    public function test_the_next_platform_inherits_the_countries(): void
    {
        $this->campaign('meta');
        $this->tool('campaign__set_targeting', ['countries' => ['US', 'IN'], 'placements' => ['facebook']]);

        $google = $this->campaign('google');

        $this->assertSame(['US', 'IN'], $google->locations['countries'] ?? null);
    }

    /**
     * The brief keeps the first answer.
     *
     * A campaign narrowed to one country afterwards must not redefine where the
     * whole brief runs; it just keeps its own list.
     */
    public function test_a_narrower_campaign_does_not_redefine_the_brief(): void
    {
        $this->campaign('meta');
        $this->tool('campaign__set_targeting', ['countries' => ['US', 'IN'], 'placements' => ['facebook']]);

        $linkedin = $this->campaign('linkedin');
        $this->tool('campaign__set_targeting', ['countries' => ['US'], 'placements' => ['linkedin_feed']]);

        $this->assertSame(['US', 'IN'], $this->brief->refresh()->locations['countries'] ?? null);
        $this->assertSame(['US'], $linkedin->refresh()->getRawOriginal('locations')
            ? json_decode($linkedin->getRawOriginal('locations'), true)['countries']
            : null);
    }

    /** Placements are still the platform's own, because they genuinely differ. */
    public function test_placements_are_not_shared(): void
    {
        $this->campaign('meta');
        $this->tool('campaign__set_targeting', ['countries' => ['US'], 'placements' => ['facebook']]);

        $google = $this->campaign('google');

        $this->assertEmpty($google->placements);
    }

    // ------------------------------------------------------------------ bidding

    /** Every platform can express all three intents. */
    public function test_each_platform_has_a_word_for_every_intent(): void
    {
        foreach ([new MetaRules, new GoogleRules, new LinkedInRules] as $rules) {
            foreach (['AUTOMATIC', 'TARGET_COST', 'BID_CAP'] as $intent) {
                $strategy = $rules->bidStrategyFor($intent, 'WEBSITE_VISITS');

                $this->assertNotNull($strategy, "{$rules->name()} has no strategy for {$intent}");
                $this->assertContains($strategy, $rules->bidStrategies(),
                    "{$rules->name()} maps {$intent} to {$strategy}, which is not on its own list");
            }
        }
    }

    /**
     * Google's automatic depends on what the campaign is for.
     *
     * Maximising clicks and maximising conversions are the same intent aimed at
     * different results, and sending the wrong one buys the wrong thing at full
     * price.
     */
    public function test_google_reads_the_objective_before_bidding_automatically(): void
    {
        $google = new GoogleRules;

        $this->assertSame('MAXIMIZE_CLICKS', $google->bidStrategyFor('AUTOMATIC', 'WEBSITE_TRAFFIC'));
        $this->assertSame('MAXIMIZE_CONVERSIONS', $google->bidStrategyFor('AUTOMATIC', 'WEBSITE_CONVERSIONS'));
    }

    /** An intent nothing recognises is refused rather than approximated. */
    public function test_an_unknown_intent_has_no_strategy(): void
    {
        $this->assertNull((new MetaRules)->bidStrategyFor('WHATEVER_IS_CHEAPEST'));
    }

    /** The defect: the same intent asked again in another vocabulary. */
    public function test_the_next_platform_inherits_the_bid_intent(): void
    {
        $this->campaign('meta', ['bid_strategy' => 'LOWEST_COST_WITHOUT_CAP', 'objective' => 'OUTCOME_TRAFFIC']);

        $linkedin = $this->campaign('linkedin', ['objective' => 'WEBSITE_VISITS']);
        $result = $this->tool('campaign__set_bidding');

        $this->assertTrue($result['from_brief'] ?? false, 'LinkedIn was asked to choose all over again.');
        $this->assertSame('AUTOMATED_BID', $linkedin->refresh()->bid_strategy);
    }

    /** And Google gets its own word, chosen for its own objective. */
    public function test_google_inherits_the_intent_in_its_own_terms(): void
    {
        $this->campaign('meta', ['bid_strategy' => 'LOWEST_COST_WITHOUT_CAP', 'objective' => 'OUTCOME_TRAFFIC']);

        $google = $this->campaign('google', ['objective' => 'WEBSITE_TRAFFIC']);
        $this->tool('campaign__set_bidding');

        $this->assertSame('MAXIMIZE_CLICKS', $google->refresh()->bid_strategy);
    }

    /** The first platform is still asked, because nothing has been decided. */
    public function test_the_first_platform_is_still_asked_how_to_bid(): void
    {
        $this->campaign('meta');

        $result = $this->tool('campaign__set_bidding');

        $this->assertArrayNotHasKey('from_brief', $result);
        $this->assertStringContainsString('on screen', $result['note']);
    }

    /**
     * The amount is not carried across, only the strategy.
     *
     * A one dollar cost cap that suits Meta may be under LinkedIn's floor, so
     * the number is asked for again even though the strategy is not.
     */
    public function test_the_amount_is_not_inherited(): void
    {
        $this->campaign('meta', [
            'bid_strategy' => 'COST_CAP', 'bid_amount' => 1, 'objective' => 'OUTCOME_TRAFFIC',
        ]);

        $linkedin = $this->campaign('linkedin', ['objective' => 'WEBSITE_VISITS']);
        $result = $this->tool('campaign__set_bidding');

        $this->assertSame('TARGET_COST', $linkedin->refresh()->bid_strategy);
        $this->assertNull($linkedin->refresh()->bid_amount);
        $this->assertStringContainsString('still need answering', $result['note']);
    }

    /** An explicit strategy still wins over the inherited one. */
    public function test_a_platform_can_still_be_given_its_own_strategy(): void
    {
        $this->campaign('meta', ['bid_strategy' => 'LOWEST_COST_WITHOUT_CAP', 'objective' => 'OUTCOME_TRAFFIC']);

        $linkedin = $this->campaign('linkedin', ['objective' => 'WEBSITE_VISITS']);
        $this->tool('campaign__set_bidding', ['strategy' => 'MAXIMUM_BID', 'target_cost' => 5]);

        $this->assertSame('MAXIMUM_BID', $linkedin->refresh()->bid_strategy);
    }
}
