<?php

namespace Tests\Feature;

use App\Agent\Choices;
use App\Agent\Tools\Campaign\ListAdAccounts;
use App\Agent\Workspace;
use App\Campaigns\NameMatch;
use App\Models\ArtifactFieldSource;
use App\Models\Campaign;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Laravel\Ai\Tools\Request as ToolRequest;
use Tests\TestCase;

/**
 * The ad account they almost certainly mean, offered first.
 *
 * It matched on the landing page URL, and the ad account is the first question
 * while the URL is the fourth, so the hint was always null and the suggestion
 * could not fire once. A browser run on 16 Sep opened with "a traffic campaign
 * on Meta for stockprices.com" and StockPrices was still listed second and
 * unbadged.
 *
 * What exists at that point is what the person typed.
 */
class AdAccountSuggestionTest extends TestCase
{
    use RefreshDatabase;

    private string $conversation = 'conv-suggest-001';

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

        config(['platforms.guardrails.allowed_ad_accounts' => ['act_111', 'act_222']]);

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

        DB::table('agent_conversations')->insert([
            'id' => $this->conversation,
            'participant_type' => $user->getMorphClass(),
            'participant_id' => $user->getKey(),
            'title' => 'Test',
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        $workspace = app(Workspace::class);
        $workspace->bindTo($this->conversation);
        $workspace->ownedBy($user);

        Http::fake([
            '*act_111*' => Http::response(['id' => 'act_111', 'name' => 'MidoWeb-WWBlog-GMT-RSOC', 'account_status' => 1, 'currency' => 'USD']),
            '*act_222*' => Http::response(['id' => 'act_222', 'name' => 'StockPrices', 'account_status' => 1, 'currency' => 'USD']),
        ]);
    }

    private function said(string $content): void
    {
        DB::table('agent_conversation_messages')->insert([
            'id' => (string) Str::uuid7(),
            'conversation_id' => $this->conversation,
            'participant_type' => User::class,
            'participant_id' => auth()->id(),
            'agent' => 'test',
            'role' => 'user',
            'content' => $content,
            'attachments' => '[]',
            'tool_calls' => '[]',
            'tool_results' => '[]',
            'usage' => '[]',
            'meta' => '[]',
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    private function campaign(array $attributes = []): Campaign
    {
        $campaign = Campaign::create(array_merge([
            'user_id' => auth()->id(),
            'name' => 'Meta campaign 16 Sep 06:45',
            'platform' => 'meta',
            'status' => 'draft',
        ], $attributes));

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

        return $campaign;
    }

    /** @return list<array<string, mixed>> */
    private function offeredOptions(): array
    {
        app(ListAdAccounts::class)->handle(new ToolRequest([]));

        return app(Choices::class)->all()[0]['options'] ?? [];
    }

    public function test_the_account_named_in_the_opening_message_is_offered_first(): void
    {
        $this->campaign();
        $this->said('I want to run a traffic campaign on Meta for stockprices.com');

        $options = $this->offeredOptions();

        // Asserted on the value, which is the account this test is about.
        // The label gained the account id in "update suggestion" on 20 Sep, so
        // two accounts sharing a name could be told apart. That is an
        // improvement, and it broke a test that was only ever checking which
        // account came first. Ranking is the behaviour; the wording is not.
        $this->assertSame('act_222', $options[0]['value']);
        $this->assertStringContainsString('StockPrices', $options[0]['label']);
        $this->assertSame('Suggested', $options[0]['badge']);
    }

    /** A suggestion, never a filter: everything stays reachable. */
    public function test_the_other_accounts_are_still_offered(): void
    {
        $this->campaign();
        $this->said('I want to run a traffic campaign on Meta for stockprices.com');

        $this->assertCount(2, $this->offeredOptions());
    }

    /** Nothing to go on means no badge, rather than a guess. */
    public function test_nothing_is_suggested_when_the_message_names_no_brand(): void
    {
        $this->campaign();
        $this->said('I want to create a new campaign please');

        foreach ($this->offeredOptions() as $option) {
            $this->assertNull($option['badge']);
        }
    }

    /**
     * A generated campaign name must not become a hint.
     *
     * Not filtered inside NameMatch, which is right to treat "sep" as a word;
     * the caller is what knows the name was invented. Proven by giving the
     * generated name something that would certainly match and checking nothing
     * is badged.
     */
    public function test_a_generated_campaign_name_is_not_used_as_a_hint(): void
    {
        $campaign = $this->campaign(['name' => 'StockPrices placeholder']);
        app(Workspace::class)->recordSource($campaign, 'name', ArtifactFieldSource::FALLBACK);

        $this->said('set something up');

        foreach ($this->offeredOptions() as $option) {
            $this->assertNull($option['badge'], 'a generated name badged '.$option['label']);
        }
    }

    /**
     * Why the caller has to exclude it rather than NameMatch.
     *
     * The generated name really does reduce to a matchable word, so a change
     * that started passing it through would badge accounts by the month.
     */
    public function test_the_generated_name_would_otherwise_match_on_the_month(): void
    {
        $this->assertContains('sep', NameMatch::words(['Meta campaign 16 Sep 06:45']));
    }

    /** A name somebody actually chose is a real hint. */
    public function test_a_chosen_campaign_name_is_used_as_a_hint(): void
    {
        $campaign = $this->campaign(['name' => 'StockPrices Q4 push']);
        app(Workspace::class)->recordSource($campaign, 'name', ArtifactFieldSource::USER_STATED);

        $this->said('set something up');

        $options = $this->offeredOptions();

        // The value, for the same reason as above: this pins which account the
        // chosen campaign name promoted, not how the option happens to read.
        $this->assertSame('act_222', $options[0]['value']);
        $this->assertStringContainsString('StockPrices', $options[0]['label']);
        $this->assertSame('Suggested', $options[0]['badge']);
    }

    /** Campaign vocabulary says nothing about which brand it is. */
    public function test_ordinary_campaign_words_are_not_treated_as_brand_names(): void
    {
        $this->assertSame(
            [],
            NameMatch::words(['I want to run a new sales campaign on Meta with a daily budget']),
        );
    }
}
