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

/**
 * Clicking two options owned by one tool must not re-ask either of them.
 *
 * Reported from arb-dev on 17 Sep: answering the budget put "What daily
 * budget?" back on screen underneath the next question, and answering targeting
 * put the placements back. Both were applied one field at a time, so the first
 * call reached its tool without the arguments the second call carried, the tool
 * treated them as unanswered, and offered them again. The answer and the
 * re-ask happened in the same turn.
 *
 * What these pin: answers are merged per tool before anything runs, so a tool
 * never sees half of what the user clicked.
 */
class ClickedAnswersMergeTest extends TestCase
{
    use RefreshDatabase;
    use SeedsVerifiedCountries;

    protected Campaign $campaign;

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

        $this->seedVerifiedCountries();

        MarketingAgent::fake(['Noted.']);

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

        $workspace = app(Workspace::class);
        $workspace->bindTo('conv-merge-0001');
        $workspace->ownedBy($user);

        $this->campaign = Campaign::create([
            'user_id' => $user->id,
            'name' => 'Winter Sale',
            'platform' => 'meta',
            'status' => 'draft',
            'currency' => 'USD',
        ]);

        $workspace->focusOn($this->campaign);
    }

    /** The exact pair of chips from the report: "daily on the ad set" and "10". */
    public function test_budget_strategy_and_amount_clicked_together_are_one_call(): void
    {
        app(Answers::class)->applyAll([
            ['field' => 'budget_strategy', 'value' => 'adset:daily'],
            ['field' => 'budget_amount', 'value' => '10'],
        ]);

        $campaign = $this->campaign->fresh();
        $this->assertEquals(10, $campaign->budget);
        $this->assertSame('adset', $campaign->budget_level);
        $this->assertSame('daily', $campaign->budget_mode);

        $this->assertSame(
            [],
            app(Choices::class)->all(),
            'The budget was answered and re-asked in the same turn.',
        );
    }

    /** Targeting is three fields on one tool, so it showed the same fault. */
    public function test_targeting_fields_clicked_together_are_one_call(): void
    {
        app(Answers::class)->applyAll([
            ['field' => 'countries', 'value' => ['US']],
            ['field' => 'placements', 'value' => ['facebook', 'instagram']],
            ['field' => 'devices', 'value' => 'mobile,desktop'],
        ]);

        $campaign = $this->campaign->fresh();
        $this->assertSame(['US'], $campaign->locations['countries'] ?? null);
        $this->assertSame(['facebook', 'instagram'], $campaign->placements);

        $offeredFields = array_column(app(Choices::class)->all(), 'field');

        $this->assertNotContains('placements', $offeredFields, 'Placements were answered, then offered again.');
        $this->assertNotContains('devices', $offeredFields, 'Devices were answered, then offered again.');
    }

    /** A capped bid strategy and its amount also share a tool. */
    public function test_bid_strategy_and_target_cost_clicked_together_are_one_call(): void
    {
        app(Answers::class)->applyAll([
            ['field' => 'bid_strategy', 'value' => 'COST_CAP'],
            ['field' => 'bid_amount', 'value' => '1'],
        ]);

        $campaign = $this->campaign->fresh();
        $this->assertSame('COST_CAP', $campaign->bid_strategy);
        $this->assertEquals(1, $campaign->bid_amount);

        $this->assertSame([], app(Choices::class)->all(), 'Nothing is left to ask.');
    }

    /** Answers for different tools must still each run. */
    public function test_answers_for_different_tools_all_apply(): void
    {
        app(Answers::class)->applyAll([
            ['field' => 'budget_strategy', 'value' => 'adset:daily'],
            ['field' => 'budget_amount', 'value' => '15'],
            ['field' => 'bid_strategy', 'value' => 'LOWEST_COST_WITHOUT_CAP'],
        ]);

        $campaign = $this->campaign->fresh();
        $this->assertEquals(15, $campaign->budget);
        $this->assertSame('LOWEST_COST_WITHOUT_CAP', $campaign->bid_strategy);
    }

    /** Junk in the posted answers must not take the turn down. */
    public function test_unknown_fields_and_bad_values_are_ignored(): void
    {
        app(Answers::class)->applyAll([
            ['field' => 'not_a_field', 'value' => 'x'],
            ['field' => 'budget_amount', 'value' => 12],
            ['value' => 'no field at all'],
            ['field' => 'budget_strategy', 'value' => 'adset:daily'],
        ]);

        $this->assertEquals(12, $this->campaign->fresh()->budget);
    }

    /**
     * "No end date" has to be clickable, and has to finish the step.
     *
     * The five lengths were the whole list, so a campaign meant to run until it
     * is paused had nothing to click, and because the step is judged by whether
     * ends_at is set it could never complete: the controller re-offered the
     * dates on every later turn.
     */
    public function test_no_end_date_is_offered_and_settles_the_schedule_step(): void
    {
        $this->campaign->update(['budget' => 10, 'budget_mode' => 'daily', 'budget_level' => 'adset']);

        $tool = collect(app(ToolRegistry::class)->resolve())
            ->first(fn ($t) => $t->name() === 'campaign__set_schedule');

        $tool->handle(new Request([]));

        $offered = collect(app(Choices::class)->all())->firstWhere('field', 'ends_at');
        $this->assertContains('', array_column($offered['options'], 'value'), 'Nothing on screen means "no end date".');

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

        $campaign = $this->campaign->fresh();
        $this->assertNull($campaign->ends_at);

        $next = app(LaunchFlow::class)->next($campaign);
        $this->assertNotSame('schedule', $next['key'] ?? null, 'The flow still wants an end date, so it asks again.');
    }

    /** A lifetime budget is the total to spend by a date, so it must keep needing one. */
    public function test_no_end_date_is_not_offered_against_a_lifetime_budget(): void
    {
        $this->campaign->update(['budget' => 500, 'budget_mode' => 'lifetime', 'budget_level' => 'adset']);

        $tool = collect(app(ToolRegistry::class)->resolve())
            ->first(fn ($t) => $t->name() === 'campaign__set_schedule');

        $tool->handle(new Request([]));

        $offered = collect(app(Choices::class)->all())->firstWhere('field', 'ends_at');
        $this->assertNotContains('', array_column($offered['options'], 'value'));
    }
}
