<?php

namespace Tests\Feature;

use App\Agent\Choices;
use App\Agent\Instructions;
use App\Agent\MarketingAgent;
use App\Agent\ToolRegistry;
use App\Agent\Workspace;
use App\Campaigns\LaunchFlow;
use App\Models\ArtifactFieldSource;
use App\Models\Brief;
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\Support\TrafficOnlyPlatformRules;
use Tests\TestCase;

/**
 * The landing page turns later questions into recommendations.
 *
 * Reading the page first is only worth doing if something uses what it found.
 * The analysis is kept on the brief so every later step can draw on it without
 * fetching the page again, and so a second platform inherits it rather than
 * asking for the page twice.
 *
 * What these pin: the analysis survives the turn, the objective arrives with a
 * suggestion and a reason, the suggestion is only ever one the platform
 * supports, and a page that says nothing useful produces no suggestion at all.
 */
class PageDrivenSuggestionsTest extends TestCase
{
    use RefreshDatabase;

    private const SHOP_PAGE = <<<'HTML'
        <html><head><title>Acme Running Shoes</title>
        <meta name="description" content="Trail shoes built for distance."></head>
        <body><h1>Acme Running Shoes</h1>
        <p>Free shipping on every order. Add to cart today.</p></body></html>
        HTML;

    private const LEAD_PAGE = <<<'HTML'
        <html><head><title>Acme Consulting</title>
        <meta name="description" content="Operations consulting for manufacturers."></head>
        <body><h1>Acme Consulting</h1>
        <p>Request a demo and we will show you the platform.</p></body></html>
        HTML;

    private const VAGUE_PAGE = <<<'HTML'
        <html><head><title>Acme</title></head>
        <body><h1>Acme</h1><p>We have been going since 1974.</p></body></html>
        HTML;

    protected User $user;

    protected Campaign $campaign;

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

        MarketingAgent::fake(['Noted.']);

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

        $workspace = app(Workspace::class);
        $workspace->bindTo('conv-suggest-0001');
        $workspace->ownedBy($this->user);

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

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

    private function runTool(string $name, array $arguments = []): array
    {
        $tool = collect(app(ToolRegistry::class)->resolve())
            ->first(fn ($t) => $t->name() === $name);

        $this->assertNotNull($tool, "Tool {$name} is not registered.");

        return json_decode((string) $tool->handle(new Request($arguments)), true);
    }

    private function readPage(string $html): array
    {
        Http::fake(['https://example.com/*' => Http::response($html)]);

        return $this->runTool('campaign__analyze_landing_page', ['url' => 'https://example.com/p']);
    }

    public function test_the_analysis_is_kept_on_the_brief(): void
    {
        $this->readPage(self::SHOP_PAGE);

        $brief = Brief::where('conversation_id', 'conv-suggest-0001')->first();

        $this->assertNotNull($brief, 'Reading the page created no brief.');
        $this->assertSame('Acme Running Shoes', $brief->pageAnalysis()?->title);
        $this->assertSame('https://example.com/p', $brief->landing_url);
    }

    /** The campaign joins the brief so a second platform inherits the answers. */
    public function test_the_campaign_in_focus_joins_the_brief(): void
    {
        $this->readPage(self::SHOP_PAGE);

        $this->assertNotNull($this->campaign->fresh()->brief_id);
    }

    public function test_a_shop_page_suggests_sales(): void
    {
        $this->readPage(self::SHOP_PAGE);

        $result = $this->runTool('campaign__choose_objective');

        $this->assertSame('OUTCOME_SALES', $result['suggested']);
    }

    public function test_a_page_asking_for_a_demo_suggests_leads(): void
    {
        $this->readPage(self::LEAD_PAGE);

        $this->assertSame('OUTCOME_LEADS', $this->runTool('campaign__choose_objective')['suggested']);
    }

    /** A wrong suggestion is worse than none, so silence is the right answer. */
    public function test_a_page_with_no_clear_signal_suggests_nothing(): void
    {
        $this->readPage(self::VAGUE_PAGE);

        $this->assertArrayNotHasKey('suggested', $this->runTool('campaign__choose_objective'));
    }

    public function test_the_suggestion_is_badged_and_carries_its_reason(): void
    {
        $this->readPage(self::SHOP_PAGE);
        $this->runTool('campaign__choose_objective');

        $options = collect(app(Choices::class)->all())->firstWhere('field', 'objective')['options'];
        $sales = collect($options)->firstWhere('value', 'OUTCOME_SALES');
        $other = collect($options)->firstWhere('value', 'OUTCOME_TRAFFIC');

        $this->assertSame('Suggested', $sales['badge']);
        $this->assertStringContainsString('add to cart', $sales['hint']);
        $this->assertNull($other['badge'], 'Only the suggested option should be badged.');
    }

    /**
     * Suggesting is not deciding.
     *
     * The objective must stay unanswered until the user picks one, or the panel
     * would show a value nobody chose as though they had.
     */
    public function test_suggesting_does_not_set_the_objective(): void
    {
        $this->readPage(self::SHOP_PAGE);
        $this->runTool('campaign__choose_objective');

        $this->assertNull($this->campaign->fresh()->objective);
        $this->assertArrayNotHasKey('objective', app(Workspace::class)->sourcesFor($this->campaign->fresh()));
    }

    /** Clicking the suggestion is the user answering, and is recorded as such. */
    public function test_accepting_the_suggestion_records_it_as_the_users_own(): void
    {
        $this->readPage(self::SHOP_PAGE);
        $this->runTool('campaign__choose_objective', ['objective' => 'OUTCOME_SALES']);

        $this->assertSame('OUTCOME_SALES', $this->campaign->fresh()->objective);
        $this->assertSame(
            ArtifactFieldSource::USER_STATED,
            app(Workspace::class)->sourcesFor($this->campaign->fresh())['objective'] ?? null,
        );
    }

    /**
     * Never suggest something the platform would reject.
     *
     * Driven through a platform that only accepts traffic, because every real
     * one accepts both outcomes the page analyser infers. Without that the
     * guard is unreachable and this test would pass whether or not it existed.
     */
    public function test_a_suggestion_the_platform_does_not_support_is_not_offered(): void
    {
        config(['platforms.rules.traffic_only' => TrafficOnlyPlatformRules::class]);

        $this->campaign->update(['platform' => 'traffic_only']);
        app(Workspace::class)->focusOn($this->campaign->fresh());

        $this->readPage(self::SHOP_PAGE);

        $result = $this->runTool('campaign__choose_objective');

        $this->assertArrayNotHasKey(
            'suggested',
            $result,
            'Sales was suggested on a platform that only accepts traffic.',
        );

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

        $this->assertSame([null], array_values(array_unique($badges)));
    }

    /**
     * The opening question, before any campaign exists.
     *
     * The flow reads a campaign, so until one existed there was no flow at all
     * and the model opened however it liked: it asked which platform to
     * advertise on. That is the question a buyer has least reason to care about
     * first, and it cannot be answered from the page.
     */
    public function test_the_first_question_is_the_landing_page_not_the_platform(): void
    {
        $opening = app(LaunchFlow::class)->beforeCampaign(null);

        $this->assertSame('landing_url', $opening['key']);
        $this->assertStringContainsString('What are you promoting?', $opening['ask']);
        $this->assertStringNotContainsStringIgnoringCase('platform', $opening['ask']);
    }

    /** Once the page is known there is nothing left to ask before the campaign. */
    public function test_the_opening_question_stops_once_the_page_is_known(): void
    {
        $this->readPage(self::SHOP_PAGE);

        $brief = Brief::where('conversation_id', 'conv-suggest-0001')->first();

        $this->assertNull(app(LaunchFlow::class)->beforeCampaign($brief));
    }

    /** The model is told not to jump ahead to the platform. */
    public function test_the_instructions_open_on_the_page_rather_than_the_platform(): void
    {
        app(Workspace::class)->clearFocus();

        $built = app(Instructions::class)->build();

        $this->assertStringContainsString('What are you promoting?', $built);
        $this->assertStringContainsString('Do not ask which platform', $built);
    }
}
