<?php

namespace Tests\Feature;

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

/**
 * The ad account question either appears or says why it did not.
 *
 * Choices::offer() ignores an empty option list, so a tool that filters its
 * accounts down to nothing changes nothing on screen. list_ad_accounts still
 * returned ok with "The accounts are on screen for the user to pick. Say one
 * short line and wait", so the model said its line and waited for a click that
 * could never come. The ad account is the first step of the LinkedIn flow, so
 * the whole conversation stopped there with nothing explaining it.
 *
 * What made that reachable rather than theoretical: an account was counted
 * usable only when status was exactly ACTIVE, and the finder is called as
 * rest/adAccounts?q=search with no projection. The fixture in
 * LinkedInRestApiTest returns id, name and currency and no status at all, and
 * against that shape every account is dropped.
 */
class AdAccountQuestionAlwaysReachesTheScreenTest extends TestCase
{
    use RefreshDatabase;

    protected Campaign $campaign;

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

        Config::set('platforms.guardrails.allowed_ad_accounts', ['556447014']);
        Config::set('platforms.linkedin.token', 'test-token');

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

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

        $this->campaign = Campaign::create([
            'user_id' => auth()->id(),
            'name' => 'LinkedIn B2B',
            'platform' => 'linkedin',
            'status' => 'draft',
        ]);

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

    /** @param list<array<string,mixed>> $elements */
    private function listAccounts(array $elements): array
    {
        Http::fake([
            'https://api.linkedin.com/rest/adAccounts*' => Http::response(['elements' => $elements]),
            '*' => Http::response([]),
        ]);

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

        $result = json_decode((string) $tool->handle(new Request([])), true);

        return [
            $result,
            collect(app(Choices::class)->all())->firstWhere('field', 'ad_account_id'),
        ];
    }

    /**
     * The regression. This shape is what the team's own fixture returns.
     */
    public function test_an_account_with_no_status_field_is_still_offered(): void
    {
        [$result, $choice] = $this->listAccounts([
            ['id' => 556447014, 'name' => 'AdMedia Corporate', 'currency' => 'USD'],
        ]);

        $this->assertTrue($result['ok'], 'A response with no status field left the buyer with nothing.');
        $this->assertNotNull($choice, 'The ad account question never reached the screen.');
        $this->assertSame(['556447014'], array_column($choice['options'], 'value'));

        // Offered as usable, not merely present. Being on screen is not enough:
        // an account the code decided was inactive is still rendered now, so
        // asserting its presence alone passes whichever way the rule reads. The
        // hint is where usable and not-usable actually differ.
        $this->assertSame(
            'USD',
            $choice['options'][0]['hint'],
            'A missing status was read as inactive rather than as unknown.',
        );

        $this->assertTrue($result['ad_accounts'][0]['usable']);
    }

    public function test_an_active_account_is_offered(): void
    {
        [, $choice] = $this->listAccounts([
            ['id' => 556447014, 'name' => 'AdMedia Corporate', 'currency' => 'USD', 'status' => 'ACTIVE'],
        ]);

        $this->assertNotNull($choice);
        $this->assertSame('USD', $choice['options'][0]['hint']);
    }

    /**
     * Shown and marked, not dropped.
     *
     * A buyer who can see the account they expect and is told why it cannot be
     * used knows what to fix. One that silently vanishes reads as a bug.
     */
    public function test_an_inactive_account_is_shown_and_marked_rather_than_dropped(): void
    {
        [$result, $choice] = $this->listAccounts([
            ['id' => 556447014, 'name' => 'AdMedia Corporate', 'currency' => 'USD', 'status' => 'CANCELED'],
        ]);

        $this->assertTrue($result['ok']);
        $this->assertNotNull($choice, 'The only account was inactive and the question disappeared.');
        $this->assertSame('Not active (canceled)', $choice['options'][0]['hint']);
    }

    /** Usable accounts come first, whatever order the platform returned them in. */
    public function test_usable_accounts_are_offered_before_unusable_ones(): void
    {
        Config::set('platforms.guardrails.allowed_ad_accounts', ['111111111', '222222222']);

        [, $choice] = $this->listAccounts([
            ['id' => 111111111, 'name' => 'Closed One', 'status' => 'CANCELED'],
            ['id' => 222222222, 'name' => 'Open One', 'status' => 'ACTIVE'],
        ]);

        $this->assertSame(['222222222', '111111111'], array_column($choice['options'], 'value'));
    }

    /**
     * Nothing to offer is reported as a failure with a cause, never as success.
     *
     * The three ways of having nothing look identical from the outside and need
     * different answers, so each is asserted separately.
     */
    public function test_no_accounts_at_all_refuses_and_says_so(): void
    {
        [$result, $choice] = $this->listAccounts([]);

        $this->assertFalse($result['ok'], 'Reported success while the screen was unchanged.');
        $this->assertNull($choice);
        $this->assertStringContainsString('cannot see them', $result['error']);
    }

    public function test_an_account_outside_the_allow_list_is_not_offered_and_refuses(): void
    {
        [$result, $choice] = $this->listAccounts([
            ['id' => 999999999, 'name' => 'Somebody Else', 'status' => 'ACTIVE'],
        ]);

        $this->assertFalse($result['ok']);
        $this->assertNull($choice, 'An account nobody allowed was put on screen.');
    }

    /** The model is told not to ask for an id it cannot use. */
    public function test_the_refusal_tells_the_model_not_to_ask_for_an_account_id(): void
    {
        [$result] = $this->listAccounts([]);

        $this->assertStringContainsString('Do not ask the user for an account id', $result['note']);
    }
}
