<?php

namespace Tests\Feature;

use App\Agent\MarketingAgent;
use App\Agent\ToolRegistry;
use App\Agent\Workspace;
use App\Models\ArtifactFieldSource;
use App\Models\Campaign;
use App\Models\User;
use App\Services\LandingPageAnalyzer;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Laravel\Ai\Tools\Request;
use RuntimeException;
use Tests\TestCase;

/**
 * Reading the page the user wants to advertise, before anything else is asked.
 *
 * This is the first step of a campaign on every platform, which is why the work
 * sits in a shared service rather than beside one platform's tools: the same
 * page decides the Meta objective and the Google keywords.
 *
 * What these pin: the page is read and summarised, the landing URL is recorded
 * as the user's own answer, nothing else is written, and the analyser cannot be
 * pointed at our own network.
 */
class AnalyzeLandingPageTest extends TestCase
{
    use RefreshDatabase;

    private const PAGE = <<<'HTML'
        <html>
            <head>
                <title>Acme Running Shoes</title>
                <meta name="description" content="Lightweight trail shoes built for long distances.">
            </head>
            <body>
                <h1>Acme Running Shoes</h1>
                <h2>Built for distance</h2>
                <p>Cushioned trail running shoes designed for marathon distance comfort and grip.</p>
            </body>
        </html>
        HTML;

    protected User $user;

    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-landing-0001');
        $workspace->ownedBy($this->user);
    }

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

        $this->assertNotNull($tool, 'The analyser is not registered.');

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

    public function test_it_reports_what_the_page_says(): void
    {
        Http::fake(['https://example.com/*' => Http::response(self::PAGE)]);

        $result = $this->runTool(['url' => 'https://example.com/shoes']);

        $this->assertTrue($result['ok']);
        $this->assertSame('Acme Running Shoes', $result['page']['title']);
        $this->assertSame('Lightweight trail shoes built for long distances.', $result['page']['description']);
        $this->assertContains('Built for distance', $result['page']['headings']);
    }

    /** The suggestions are the point: they turn later questions into confirmations. */
    public function test_it_suggests_a_campaign_name_and_copy(): void
    {
        Http::fake(['https://example.com/*' => Http::response(self::PAGE)]);

        $result = $this->runTool(['url' => 'https://example.com/shoes']);

        $this->assertSame('Acme Running Shoes', $result['suggestions']['campaign_name']);
        $this->assertContains('Acme Running Shoes', $result['suggestions']['headlines']);
        $this->assertNotEmpty($result['suggestions']['keywords']);
    }

    /**
     * The campaign name must not name a platform.
     *
     * One brief can become a campaign on several platforms, so "… - Google Ads"
     * would be wrong on every platform but one.
     */
    public function test_the_suggested_campaign_name_is_platform_neutral(): void
    {
        Http::fake(['https://example.com/*' => Http::response(self::PAGE)]);

        $name = $this->runTool(['url' => 'https://example.com/shoes'])['suggestions']['campaign_name'];

        $this->assertStringNotContainsStringIgnoringCase('google', $name);
        $this->assertStringNotContainsStringIgnoringCase('meta', $name);
    }

    public function test_it_records_the_url_on_the_campaign_in_focus_as_user_stated(): void
    {
        Http::fake(['https://example.com/*' => Http::response(self::PAGE)]);

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

        $this->runTool(['url' => 'https://example.com/shoes']);

        $this->assertSame('https://example.com/shoes', $campaign->fresh()->landing_url);
        $this->assertSame(
            ArtifactFieldSource::USER_STATED,
            app(Workspace::class)->sourcesFor($campaign->fresh())['landing_url'] ?? null,
        );
    }

    /** It runs before a campaign exists, so it must not need one. */
    public function test_it_works_with_no_campaign_in_focus(): void
    {
        Http::fake(['https://example.com/*' => Http::response(self::PAGE)]);

        $this->assertTrue($this->runTool(['url' => 'https://example.com/shoes'])['ok']);
    }

    public function test_a_missing_url_asks_for_one_rather_than_failing(): void
    {
        $result = $this->runTool([]);

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('ask the user', strtolower($result['error']));
    }

    public function test_an_unreadable_page_reports_the_reason(): void
    {
        Http::fake(['https://example.com/*' => Http::response('', 404)]);

        $result = $this->runTool(['url' => 'https://example.com/gone']);

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('404', $result['error']);
    }

    /**
     * The URL comes from whoever is chatting and the server fetches it.
     *
     * Without this the chat is a request-forgery tool aimed at our own network.
     */
    public function test_it_refuses_private_and_local_addresses(): void
    {
        foreach (['http://localhost/admin', 'http://127.0.0.1/', 'http://thing.local/'] as $url) {
            $result = $this->runTool(['url' => $url]);

            $this->assertFalse($result['ok'], "{$url} was allowed.");
            $this->assertStringContainsString('public landing page', $result['error']);
        }
    }

    public function test_an_unresolvable_host_says_so_rather_than_blaming_the_network(): void
    {
        $result = $this->runTool(['url' => 'https://not-a-real-domain.invalid/']);

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('could not be found', $result['error']);
    }

    /** The service is the one implementation; the MCP tool and this one both call it. */
    public function test_the_service_refuses_private_hosts_directly(): void
    {
        $this->expectException(RuntimeException::class);

        app(LandingPageAnalyzer::class)->analyze('http://127.0.0.1/');
    }
}
