<?php

namespace Tests\Feature;

use App\Agent\Workspace;
use App\Campaigns\LaunchFlow;
use App\Models\Campaign;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;

/**
 * The order a campaign gets built in.
 *
 * Clickable options made each answer easy, and the agent still waited to be
 * asked what came next, so the user was driving a flow they were meant to be
 * led through. This derives the next step from what the campaign holds, so the
 * answer is the same every turn and survives the user doing things out of order.
 */
class LaunchFlowTest extends TestCase
{
    use RefreshDatabase;

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

    /**
     * The first question is what is being advertised.
     *
     * It used to be the ad account, which asked the buyer to do administration
     * before they had said what they wanted. The landing page is the one thing
     * they arrive knowing, and reading it is what lets every later step be a
     * recommendation rather than a blank question.
     */
    public function test_an_empty_campaign_starts_at_the_landing_page(): void
    {
        $next = app(LaunchFlow::class)->next($this->campaign());

        $this->assertSame('landing_url', $next['key']);
        $this->assertSame('analyze_landing_page', $next['tool']);
    }

    /**
     * The shared questions come first, then the platform's own.
     *
     * A second platform therefore repeats only the middle of the flow, which is
     * the whole reason the brief exists.
     */
    public function test_the_shared_questions_are_asked_before_the_platforms_own(): void
    {
        $keys = array_column(app(LaunchFlow::class)->progress($this->campaign()), 'key');

        $this->assertSame('landing_url', $keys[0]);
        $this->assertLessThan(
            array_search('ad_account', $keys, true),
            array_search('budget', $keys, true),
            'The budget is shared and should be settled before platform setup.',
        );
        $this->assertContains('creatives', $keys);
    }

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

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

        $campaign->update(['budget' => 20, 'budget_mode' => 'daily']);

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

        $campaign->update(['ad_account_id' => 'act_777']);

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

    /**
     * Declared-as-none is an answer; never-asked is not.
     *
     * The whole reason the column is nullable, and the flow has to respect the
     * same distinction or it would skip the question.
     */
    public function test_an_undeclared_category_is_not_treated_as_answered(): void
    {
        $campaign = $this->campaign([
            'landing_url' => 'https://example.com',
            'budget' => 20,
            'budget_mode' => 'daily',
            'ad_account_id' => 'act_777',
            'objective' => 'OUTCOME_TRAFFIC',
        ]);

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

        $campaign->update(['special_ad_categories' => ['NONE']]);

        $this->assertNotSame('special_ad_categories', app(LaunchFlow::class)->next($campaign->fresh())['key']);
    }

    /**
     * A step that does not apply is not an outstanding step.
     *
     * Traffic needs no pixel, so asking for one would be inventing work.
     */
    public function test_a_traffic_campaign_is_never_asked_for_a_pixel(): void
    {
        $campaign = $this->campaign([
            'landing_url' => 'https://example.com',
            'budget' => 20,
            'budget_mode' => 'daily',
            'ad_account_id' => 'act_777',
            'objective' => 'OUTCOME_TRAFFIC',
            'special_ad_categories' => ['NONE'],
            'page_id' => 'page-1',
        ]);

        $keys = array_column(app(LaunchFlow::class)->progress($campaign), 'key');

        $this->assertNotContains('pixel', $keys);
        $this->assertSame('bidding', app(LaunchFlow::class)->next($campaign)['key']);
    }

    public function test_a_leads_campaign_is_asked_for_a_pixel(): void
    {
        $campaign = $this->campaign([
            'landing_url' => 'https://example.com',
            'budget' => 20,
            'budget_mode' => 'daily',
            'ad_account_id' => 'act_777',
            'objective' => 'OUTCOME_LEADS',
            'special_ad_categories' => ['NONE'],
            'page_id' => 'page-1',
        ]);

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

    /** Progress is what a person is shown, so it has to count only real steps. */
    public function test_progress_reports_what_is_settled(): void
    {
        $campaign = $this->campaign([
            'ad_account_id' => 'act_777',
            'objective' => 'OUTCOME_TRAFFIC',
        ]);

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

        $this->assertTrue(collect($progress)->firstWhere('key', 'ad_account')['done']);
        $this->assertFalse(collect($progress)->firstWhere('key', 'budget')['done']);
    }

    public function test_a_complete_campaign_has_no_next_step(): void
    {
        $campaign = $this->campaign([
            'ad_account_id' => 'act_777',
            'objective' => 'OUTCOME_TRAFFIC',
            'special_ad_categories' => ['NONE'],
            'landing_url' => 'https://example.com',
            'page_id' => 'page-1',
            'budget' => 20,
            'bid_strategy' => 'LOWEST_COST_WITHOUT_CAP',
            'locations' => ['countries' => ['US']],
            'placements' => ['facebook'],
            'utm' => '',
        ]);

        $campaign->ads()->create([
            'headline' => 'Winter Sale',
            'primary_text' => 'Half price until Sunday.',
            'cta' => 'SHOP_NOW',
            'asset_id' => null,
        ]);

        // Copy exists but no creative is attached, so it is not finished.
        $this->assertSame('creatives', app(LaunchFlow::class)->next($campaign->fresh())['key']);
    }

    // ------------------------------------------------- shown to the buyer

    /**
     * The panel has to say where they are.
     *
     * LaunchFlow has always computed this to decide the next question, and
     * nothing rendered it, so the product could walk somebody through fourteen
     * steps without ever saying how many there were or how far along they got.
     */
    public function test_the_panel_shows_how_far_along_the_campaign_is(): void
    {
        $user = User::factory()->create();
        $this->actingAs($user);

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

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

        $campaign = $this->campaign([
            'ad_account_id' => 'act_777',
            'objective' => 'OUTCOME_TRAFFIC',
            'special_ad_categories' => ['NONE'],
        ]);
        $workspace->produced($campaign);
        $workspace->focusOn($campaign);

        $html = $this->get(route('chat.show', ['conversation' => $conversation]))
            ->assertOk()
            ->getContent();

        // Three of the steps are answered, and a traffic campaign has no pixel
        // step at all, so it is not counted against them.
        $this->assertStringContainsString('3 of', $html);
        $this->assertStringContainsString('Next:', $html);
        $this->assertStringContainsString('mb-progress-step', $html);
        $this->assertStringNotContainsString('>pixel<', $html);
    }

    /**
     * Bidding was a tool nothing ever reached.
     *
     * SetBidding has existed since the start and the flow never asked, so every
     * campaign was published on whatever the default happened to be, with the
     * panel showing a strategy nobody chose.
     */
    public function test_the_flow_asks_how_it_should_bid(): void
    {
        $campaign = $this->campaign([
            'ad_account_id' => 'act_777',
            'objective' => 'OUTCOME_TRAFFIC',
            'special_ad_categories' => ['NONE'],
            'landing_url' => 'https://example.com',
            'page_id' => 'page-1',
            'budget' => 20,
        ]);

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

    /**
     * A campaign that runs until someone stops it needs no end date, so asking
     * for one would be a step that does not exist. A lifetime budget is the
     * total to spend by a date, so there it does.
     */
    public function test_an_end_date_is_only_asked_for_when_the_budget_is_a_lifetime_one(): void
    {
        $settled = [
            'ad_account_id' => 'act_777',
            'objective' => 'OUTCOME_TRAFFIC',
            'special_ad_categories' => ['NONE'],
            'landing_url' => 'https://example.com',
            'page_id' => 'page-1',
            'budget' => 20,
            'bid_strategy' => 'LOWEST_COST_WITHOUT_CAP',
        ];

        $daily = $this->campaign($settled);

        $this->assertNotContains('schedule', array_column(app(LaunchFlow::class)->progress($daily), 'key'));
        $this->assertSame('targeting', app(LaunchFlow::class)->next($daily)['key']);

        $lifetime = $this->campaign([...$settled, 'budget_mode' => 'lifetime']);

        $this->assertSame('schedule', app(LaunchFlow::class)->next($lifetime)['key']);
    }
}
