<?php

namespace Tests\Feature;

use App\Agent\Choices;
use App\Agent\ToolRegistry;
use App\Agent\Workspace;
use App\Campaigns\NameMatch;
use App\Models\Campaign;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Laravel\Ai\Tools\Request;
use Tests\TestCase;

/**
 * Offering the one they almost certainly mean, first.
 *
 * A buyer advertising stockprices.com should not have to find the StockPrices
 * Page among fifty. The landing page already says which brand this is, so the
 * Page that matches it is offered first and badged.
 *
 * Two rules matter. It stays a reordering, because the wrong Page is somebody
 * else's brand publishing somebody else's ad, so a bad guess must cost a scroll
 * rather than the right answer. And when nothing matches the brand, nothing is
 * suggested: a guess drawn from the ad account's name is not a weaker signal
 * about the brand, it is a signal about something else entirely.
 */
class SuggestionTest extends TestCase
{
    use RefreshDatabase;

    protected Campaign $campaign;

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

        config([
            'platforms.guardrails.allowed_ad_accounts' => ['act_777'],
            'platforms.meta.mcp.enabled' => false,
            'platforms.meta.token' => 'test-token',
        ]);

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

        $workspace = app(Workspace::class);
        $workspace->bindTo('conv-suggest-001');

        $this->campaign = Campaign::create([
            'user_id' => auth()->id(),
            'name' => 'Winter Sale',
            'platform' => 'meta',
            'status' => 'draft',
            'objective' => 'OUTCOME_TRAFFIC',
            'ad_account_id' => 'act_777',
        ]);

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

    private function offerPages(): array
    {
        Http::fake([
            '*/me/accounts*' => Http::response(['data' => [
                ['id' => '1', 'name' => 'Lucos.com', 'category' => 'Software company'],
                ['id' => '2', 'name' => 'Leisure.com', 'category' => 'Travel'],
                ['id' => '3', 'name' => 'Stockprices.com', 'category' => 'Finance'],
                ['id' => '4', 'name' => 'IBlockAds', 'category' => 'Advertising/marketing'],
            ]]),
            '*/act_777*' => Http::response(['id' => 'act_777', 'name' => 'StockPrices', 'account_status' => 1]),
            '*' => Http::response(['data' => []]),
        ]);

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

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

        return collect(app(Choices::class)->all())->firstWhere('field', 'page_id')['options'];
    }

    /**
     * Matched on the landing page, which is who is advertising.
     *
     * This asserted a match on the ad account's name, and the campaign it ran
     * against had no landing page at all, so the account fallback was the only
     * thing it ever exercised. That fallback is gone: see the test below for
     * what it did to a brand with no Page of its own.
     */
    public function test_the_page_matching_the_landing_page_is_offered_first(): void
    {
        $this->campaign->update(['landing_url' => 'https://stockprices.com/']);

        $options = $this->offerPages();

        $this->assertSame('Stockprices.com', $options[0]['label']);
        $this->assertSame('Suggested', $options[0]['badge']);
    }

    /**
     * A brand with no Page of its own is offered nothing, not somebody else's.
     *
     * Shyam, 23 Sep: "for attestpath it was suggesting midoweb pages". The ad
     * account was MidoWeb-WWBlog-GMT-RSOC, attestpath.ai matched no Page, and
     * the hints fell through to the account's name, so every MidoWeb Page was
     * badged Suggested. That is a recommendation to publish as an unrelated
     * brand, made with the same confidence as a real match.
     */
    public function test_no_page_is_suggested_when_none_matches_the_brand(): void
    {
        $this->campaign->update(['landing_url' => 'https://attestpath.ai/']);

        $badges = array_filter(array_column($this->offerPages(), 'badge'));

        $this->assertSame([], $badges, 'A Page for another brand was suggested.');
    }

    /** And every Page is still offered, because the buyer still has to pick one. */
    public function test_every_page_is_still_offered_when_nothing_matches(): void
    {
        $this->campaign->update(['landing_url' => 'https://attestpath.ai/']);

        $this->assertCount(4, $this->offerPages());
    }

    /** A suggestion, not a filter. The wrong Page must still be reachable. */
    public function test_every_other_page_is_still_offered(): void
    {
        $labels = array_column($this->offerPages(), 'label');

        $this->assertCount(4, $labels);
        $this->assertContains('Lucos.com', $labels);
        $this->assertContains('IBlockAds', $labels);
    }

    public function test_only_the_match_is_badged(): void
    {
        $this->campaign->update(['landing_url' => 'https://stockprices.com/']);

        $badges = array_filter(array_column($this->offerPages(), 'badge'));

        $this->assertCount(1, $badges, 'Badging everything says nothing.');
    }

    /** The landing page answers it too, when no account has been chosen yet. */
    public function test_the_landing_page_domain_can_supply_the_match(): void
    {
        $this->campaign->update(['ad_account_id' => 'act_777', 'landing_url' => 'https://www.leisure.com/deals']);

        Http::fake([
            '*/me/accounts*' => Http::response(['data' => [
                ['id' => '1', 'name' => 'Lucos.com'],
                ['id' => '2', 'name' => 'Leisure.com'],
            ]]),
            // The account name gives nothing, so the domain has to.
            '*/act_777*' => Http::response(['id' => 'act_777', 'name' => 'Main Account', 'account_status' => 1]),
            '*' => Http::response(['data' => []]),
        ]);

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

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

        $options = collect(app(Choices::class)->all())->firstWhere('field', 'page_id')['options'];

        $this->assertSame('Leisure.com', $options[0]['label']);
    }

    // ------------------------------------------------------------ the matcher

    /**
     * Generic words must not match.
     *
     * "ads" appears in half the Page names on this account, so matching on it
     * would badge everything and mean nothing.
     */
    public function test_generic_words_are_not_matched_on(): void
    {
        // Every part of this is a word half the Pages on the account share, so
        // there is nothing here worth matching and it suggests nothing.
        $this->assertSame([], NameMatch::words(['https://www.the-ads-hub.com/']));

        // The brand survives; only the shared parts are dropped.
        $this->assertSame(['stockprices'], NameMatch::words(['https://www.stockprices-ads.com/']));
    }

    public function test_a_url_reduces_to_its_host(): void
    {
        $this->assertSame(['stockprices'], NameMatch::words(['https://www.stockprices.com/offer?ref=x']));
    }

    /** An account name and a domain have to reduce to the same thing. */
    public function test_a_name_and_a_domain_agree(): void
    {
        $this->assertSame(
            NameMatch::words(['StockPrices']),
            NameMatch::words(['https://stockprices.com']),
        );
    }

    public function test_nothing_is_suggested_when_there_is_no_hint(): void
    {
        $items = [['name' => 'Lucos.com'], ['name' => 'Leisure.com']];

        $ranked = NameMatch::suggestFirst($items, [null]);

        $this->assertSame($items, $ranked, 'With no hint the order should be untouched.');
    }
}
