<?php

namespace Tests\Feature;

use App\Agent\Workspace;
use App\Campaigns\LaunchFlow;
use App\Http\Controllers\ChatController;
use App\Models\Campaign;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

/**
 * A turn always leaves the person something to do.
 *
 * Most steps put options on screen, and the controller supplies those itself
 * when the model does not. Two steps cannot: a landing page URL and ad copy
 * have no list to choose from, so they have to be asked for in words. The model
 * is told to ask and sometimes returns nothing, and the turn then ended on
 * "Updated the campaign. Still needed: landing page URL not set" with no
 * question in it. Observed in the browser on 16 Sep.
 */
class NoDeadEndTest extends TestCase
{
    use RefreshDatabase;

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

        $this->actingAs(User::factory()->create());
        app(Workspace::class)->bindTo('conv-dead-end-001');
    }

    private function campaign(array $attributes = []): Campaign
    {
        return Campaign::create(array_merge([
            'user_id' => auth()->id(),
            'name' => 'Winter Sale',
            'platform' => 'meta',
            'status' => 'draft',
        ], $attributes));
    }

    /**
     * Every step either offers something to click or carries its own question.
     *
     * Written over the whole table rather than the two known cases, so a step
     * added later with no options cannot quietly reintroduce the dead end.
     */
    public function test_every_step_without_options_carries_a_question(): void
    {
        $steps = app(LaunchFlow::class)->steps($this->campaign());

        foreach ($steps as $step) {
            if ($step['offers']) {
                continue;
            }

            $this->assertArrayHasKey('ask', $step, "step [{$step['key']}] offers nothing and has no question");
            $this->assertNotEmpty($step['ask'], "step [{$step['key']}] has an empty question");
        }
    }

    public function test_the_landing_page_step_asks_for_a_url(): void
    {
        $campaign = $this->campaign([
            'ad_account_id' => 'act_777',
            'objective' => 'OUTCOME_TRAFFIC',
            'special_ad_categories' => ['NONE'],
        ]);

        $next = app(LaunchFlow::class)->next($campaign);

        $this->assertSame('landing_url', $next['key']);
        $this->assertStringContainsString('URL', $next['ask']);
    }

    /**
     * The question reaches the screen when the model says nothing.
     *
     * Driven through the controller rather than the flow, because the bug was
     * that the two never met: the flow knew what it needed and the reply was
     * built from the publish gate instead.
     */
    public function test_a_silent_turn_still_asks_for_the_landing_page(): void
    {
        $campaign = $this->campaign([
            'ad_account_id' => 'act_777',
            'objective' => 'OUTCOME_TRAFFIC',
            'special_ad_categories' => ['NONE'],
        ]);

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

        $controller = app(ChatController::class);

        $fallback = (new \ReflectionClass($controller))->getMethod('fallbackText');
        $fallback->setAccessible(true);

        $reply = $fallback->invoke($controller, []);

        $this->assertStringContainsString('URL', $reply);
        $this->assertStringNotContainsString('Still needed', $reply);
    }

    /** A step that does have options is left to the options, not asked twice. */
    public function test_a_step_with_options_is_not_also_asked_in_words(): void
    {
        $campaign = $this->campaign();

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

        // The landing page is asked first and is typed rather than clicked, so
        // the step under test here is the first one that does offer options.
        $campaign->update(['landing_url' => 'https://example.com']);

        $this->assertSame('budget', app(LaunchFlow::class)->next($campaign->fresh())['key']);

        $controller = app(ChatController::class);
        $fallback = (new \ReflectionClass($controller))->getMethod('fallbackText');
        $fallback->setAccessible(true);

        $this->assertStringContainsString('Still needed', $fallback->invoke($controller, []));
    }

    /** An approval on screen is not a missing answer, and still takes priority. */
    public function test_a_pending_approval_still_wins(): void
    {
        $campaign = $this->campaign([
            'ad_account_id' => 'act_777',
            'objective' => 'OUTCOME_TRAFFIC',
            'special_ad_categories' => ['NONE'],
        ]);

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

        $controller = app(ChatController::class);
        $fallback = (new \ReflectionClass($controller))->getMethod('fallbackText');
        $fallback->setAccessible(true);

        $reply = $fallback->invoke($controller, [['id' => 'x', 'tool' => 'publish campaign', 'reason' => null]]);

        $this->assertStringContainsString('approval', $reply);
    }
}
