<?php

namespace Tests\Feature;

use App\Agent\Choices;
use App\Agent\ToolRegistry;
use App\Agent\Workspace;
use App\Models\AllowedAdAccount;
use App\Models\Campaign;
use App\Models\User;
use App\Services\LinkedIn\LinkedInRestApi;
use App\Services\Platforms\Support\Guardrails;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Laravel\Ai\Tools\Request;
use Tests\TestCase;

/**
 * "meta:*" opens Meta and says nothing about anyone else.
 *
 * On dev the allowed accounts change often enough that enumerating them is the
 * reason somebody eventually turns the guard off altogether, which is worse
 * than a wildcard because it is invisible. A prefixed star is the smallest
 * thing that removes the chore while still naming which platform it opens.
 *
 * A bare "*" is deliberately not honoured. entryFor() reads an unprefixed entry
 * as Google or LinkedIn and never Meta, so a lone star would open two platforms
 * and leave the third shut while reading like it opened everything.
 */
class APlatformWildcardOpensOnePlatformTest extends TestCase
{
    use RefreshDatabase;

    private function allowing(array $entries): Guardrails
    {
        config(['platforms.guardrails.allowed_ad_accounts' => $entries]);

        return app(Guardrails::class);
    }

    public function test_a_platform_wildcard_allows_any_account_on_that_platform(): void
    {
        $guard = $this->allowing(['meta:*']);

        $this->assertTrue($guard->allows('act_999999999999', 'meta'));
        $this->assertTrue($guard->allows('act_111111111111', 'meta'));
    }

    /** And leaves the other platforms exactly as shut as they were. */
    public function test_one_platforms_wildcard_does_not_open_another(): void
    {
        $guard = $this->allowing(['meta:*']);

        $this->assertFalse($guard->allows('3460855874', 'google'));
        $this->assertFalse($guard->allows('556447014', 'linkedin'));
    }

    /** Each platform can be opened on its own terms. */
    public function test_every_platform_can_carry_its_own_wildcard(): void
    {
        $guard = $this->allowing(['meta:*', 'google:*', 'linkedin:*']);

        $this->assertTrue($guard->allows('act_2220667645134722', 'meta'));
        $this->assertTrue($guard->allows('346-085-5874', 'google'));
        $this->assertTrue($guard->allows('urn:li:sponsoredAccount:556447014', 'linkedin'));
    }

    /**
     * A bare star opens nothing.
     *
     * Refused rather than guessed at, because the unprefixed rule would have
     * made it mean Google and LinkedIn but not Meta.
     */
    public function test_a_bare_star_is_not_a_wildcard(): void
    {
        $guard = $this->allowing(['*']);

        foreach (['meta' => 'act_1', 'google' => '123', 'linkedin' => '456'] as $platform => $id) {
            $this->assertFalse($guard->allows($id, $platform), "a bare star opened {$platform}");
        }
    }

    /**
     * The wildcard is not turned into an account id.
     *
     * Meta's branch adds the act_ prefix to anything without one, so an
     * unguarded wildcard became act_*, matched nothing, and made "meta:*" mean
     * the opposite of what it says.
     */
    public function test_the_wildcard_is_not_normalised_into_an_account(): void
    {
        $this->assertSame('*', AllowedAdAccount::normalizeAccountId('*', 'meta'));
        $this->assertSame('*', AllowedAdAccount::normalizeAccountId('*', 'google'));
        $this->assertSame('*', AllowedAdAccount::normalizeAccountId('*', 'linkedin'));
    }

    /** Named accounts still work alongside a wildcard for another platform. */
    public function test_named_accounts_still_work_beside_a_wildcard(): void
    {
        $guard = $this->allowing(['meta:*', '3460855874']);

        $this->assertTrue($guard->allows('act_777', 'meta'));
        $this->assertTrue($guard->allows('3460855874', 'google'));
        $this->assertFalse($guard->allows('9999999999', 'google'));
    }

    /** It works from the database too, which is where the list now lives. */
    public function test_a_wildcard_row_in_the_table_opens_the_platform(): void
    {
        config(['platforms.guardrails.allowed_ad_accounts' => []]);

        AllowedAdAccount::create([
            'platform' => 'linkedin',
            'account_id' => '*',
            'name' => 'All LinkedIn accounts (dev)',
            'is_active' => true,
        ]);

        $guard = app(Guardrails::class);

        $this->assertTrue($guard->allows('556447014', 'linkedin'));
        $this->assertFalse($guard->allows('act_1', 'meta'));
    }

    /** An inactive wildcard opens nothing, like any other inactive row. */
    public function test_an_inactive_wildcard_is_ignored(): void
    {
        config(['platforms.guardrails.allowed_ad_accounts' => []]);

        AllowedAdAccount::create([
            'platform' => 'linkedin',
            'account_id' => '*',
            'is_active' => false,
        ]);

        $this->assertFalse(app(Guardrails::class)->allows('556447014', 'linkedin'));
    }

    /**
     * A wildcard offers every account, rather than none.
     *
     * The chooser enumerates the allow list and fetches each id, and "*" is a
     * rule rather than an account: asking Meta for an account called "*" fails,
     * so on a purely wildcarded platform nothing reached the screen and the
     * buyer had to type the id by hand. Kaushal hit exactly that.
     */
    public function test_a_wildcard_lists_every_account_the_token_can_see(): void
    {
        config([
            'platforms.guardrails.allowed_ad_accounts' => ['meta:*'],
            'platforms.meta.token' => 'test-token',
        ]);

        Http::fake([
            '*me/adaccounts*' => Http::response(['data' => [
                ['id' => 'act_111', 'name' => 'First', 'account_status' => 1, 'currency' => 'USD'],
                ['id' => 'act_222', 'name' => 'Second', 'account_status' => 1, 'currency' => 'USD'],
            ]]),
            '*' => Http::response(['data' => []]),
        ]);

        $user = User::factory()->create();
        $this->actingAs($user);
        app(Workspace::class)->bindTo('conv-wildcard-accounts');

        $campaign = Campaign::create([
            'user_id' => $user->id, 'name' => 'Meta campaign',
            'platform' => 'meta', 'status' => 'draft',
        ]);
        app(Workspace::class)->focusOn($campaign);

        $result = json_decode((string) collect(app(ToolRegistry::class)->resolve())
            ->first(fn ($t) => $t->name() === 'campaign__list_ad_accounts')
            ->handle(new Request([])), true);

        $this->assertTrue($result['ok'] ?? false, $result['error'] ?? '');

        $offered = json_encode(app(Choices::class)->all());
        $this->assertStringContainsString('act_111', $offered);
        $this->assertStringContainsString('act_222', $offered);
    }

    /**
     * The same on LinkedIn, which filtered rather than fetched.
     *
     * Its branch already lists from the API and keeps only the allowed ids, so
     * the star was compared as though it were an account: nothing matched, and
     * "linkedin:*" offered fewer accounts than naming one would have.
     */
    public function test_a_linkedin_wildcard_does_not_filter_the_list_away(): void
    {
        config(['platforms.guardrails.allowed_ad_accounts' => ['linkedin:*']]);

        $this->mock(LinkedInRestApi::class)
            ->shouldReceive('adAccounts')
            ->andReturn([
                ['id' => '556447014', 'name' => 'First', 'currency' => 'USD', 'status' => 'ACTIVE'],
                ['id' => '998877665', 'name' => 'Second', 'currency' => 'USD', 'status' => 'ACTIVE'],
            ]);

        $user = User::factory()->create();
        $this->actingAs($user);
        app(Workspace::class)->bindTo('conv-wildcard-linkedin');

        $campaign = Campaign::create([
            'user_id' => $user->id, 'name' => 'LinkedIn campaign',
            'platform' => 'linkedin', 'status' => 'draft',
        ]);
        app(Workspace::class)->focusOn($campaign);

        $result = json_decode((string) collect(app(ToolRegistry::class)->resolve())
            ->first(fn ($t) => $t->name() === 'campaign__list_ad_accounts')
            ->handle(new Request([])), true);

        $this->assertTrue($result['ok'] ?? false, $result['error'] ?? '');
        $this->assertCount(2, $result['ad_accounts']);
    }

    /**
     * Naming a LinkedIn account by hand works under a wildcard.
     *
     * verify_ad_account kept its own copy of the allow-list check, enumerating
     * permittedAccounts() and comparing ids. A wildcard is a rule rather than an
     * account, so the list was ['*'], nothing matched it, and every LinkedIn
     * account was refused on the one platform deliberately opened to all of
     * them - while Guardrails::allows() answered true for the same id. This is
     * the third and last caller that enumerated rather than asking.
     */
    public function test_a_named_linkedin_account_is_accepted_under_a_wildcard(): void
    {
        config(['platforms.guardrails.allowed_ad_accounts' => ['linkedin:*']]);

        $this->mock(LinkedInRestApi::class)
            ->shouldReceive('adAccount')
            ->andReturn(['id' => '556447014', 'name' => 'Some Account', 'status' => 'ACTIVE', 'currency' => 'USD']);

        $campaign = $this->linkedInCampaign();

        $result = $this->verify('urn:li:sponsoredAccount:556447014');

        $this->assertTrue($result['ok'] ?? false, $result['error'] ?? '');
        $this->assertSame('556447014', $campaign->refresh()->ad_account_id);
    }

    /** And a wildcard for another platform still refuses LinkedIn. */
    public function test_a_named_linkedin_account_is_refused_when_only_meta_is_open(): void
    {
        config(['platforms.guardrails.allowed_ad_accounts' => ['meta:*']]);

        $this->mock(LinkedInRestApi::class)
            ->shouldReceive('adAccount')
            ->andReturn(['id' => '556447014', 'status' => 'ACTIVE']);

        $this->linkedInCampaign();

        $this->assertFalse($this->verify('urn:li:sponsoredAccount:556447014')['ok'] ?? true);
    }

    private function linkedInCampaign(): Campaign
    {
        $user = User::factory()->create();
        $this->actingAs($user);
        app(Workspace::class)->bindTo('conv-verify-wildcard');

        $campaign = Campaign::create([
            'user_id' => $user->id, 'name' => 'LinkedIn campaign',
            'platform' => 'linkedin', 'status' => 'draft',
        ]);
        app(Workspace::class)->focusOn($campaign);

        return $campaign;
    }

    /** @return array<string,mixed> */
    private function verify(string $id): array
    {
        return json_decode((string) collect(app(ToolRegistry::class)->resolve())
            ->first(fn ($t) => $t->name() === 'campaign__verify_ad_account')
            ->handle(new Request(['ad_account_id' => $id])), true);
    }
}
