<?php

namespace Tests\Unit\Services\AI;

use App\Exceptions\ProviderNotConfiguredException;
use App\Jobs\ScrapeMetaAdLibraryJob;
use App\Services\AI\PuppeteerMetaAdLibraryProvider;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Queue;
use RuntimeException;
use Tests\TestCase;

class PuppeteerMetaAdLibraryProviderTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        config([
            'creative.puppeteer_ad_library.enabled' => true,
            'creative.puppeteer_ad_library.node_binary' => 'node',
            'creative.puppeteer_ad_library.script' => 'scripts/ad-library/scrape-ad-library.js',
            'creative.puppeteer_ad_library.timeout_seconds' => 180,
            'creative.puppeteer_ad_library.headless' => 'true',
            'creative.puppeteer_ad_library.max_ads' => 20,
        ]);
    }

    public function test_search_opens_the_ad_library_url_with_keyword_country_and_media_type(): void
    {
        $this->fakeSuccessfulScrape();

        (new PuppeteerMetaAdLibraryProvider)->search('adidas', 'IN', null, 'IMAGE');

        Process::assertRan(function ($process): bool {
            $command = is_array($process->command) ? implode(' ', $process->command) : $process->command;

            return str_contains($command, 'scrape-ad-library.js')
                && str_contains($command, 'facebook.com/ads/library')
                && str_contains($command, 'q=adidas')
                && str_contains($command, 'country=IN')
                && str_contains($command, 'media_type=image')
                && str_contains($command, 'search_type=keyword_unordered')
                && ($process->environment['AD_LIBRARY_PUPPETEER_HEADLESS'] ?? null) === 'true';
        });
    }

    public function test_search_maps_scraped_ads_for_the_wizard_cards(): void
    {
        $this->fakeSuccessfulScrape([[
            'id' => '1249043200627555',
            'pageName' => 'adidas',
            'title' => 'Shop adidas',
            'body' => 'Impossible is nothing.',
            'linkDomain' => 'adidas.com',
            'startDate' => '2026-03-17',
            'images' => [['resizedUrl' => 'https://cdn.example/thumb.jpg']],
            'videos' => [],
            'adLibraryUrl' => 'https://www.facebook.com/ads/library/?id=1249043200627555',
        ]]);

        $ad = (new PuppeteerMetaAdLibraryProvider)->search('adidas')->first();

        $this->assertSame('1249043200627555', $ad->externalAdId);
        $this->assertSame('adidas', $ad->advertiserName);
        $this->assertSame('https://cdn.example/thumb.jpg', $ad->thumbnailUrl);
        $this->assertSame(['Impossible is nothing.'], $ad->snapshot['ad_creative_bodies']);
        $this->assertSame(['Shop adidas'], $ad->snapshot['ad_creative_link_titles']);
        $this->assertSame(['adidas.com'], $ad->snapshot['ad_creative_link_captions']);
        $this->assertSame('https://www.facebook.com/ads/library/?id=1249043200627555', $ad->snapshot['ad_snapshot_url']);
        $this->assertSame('2026-03-17', $ad->snapshot['ad_delivery_start_time']);
    }

    public function test_search_uses_the_listing_video_poster_as_the_wizard_thumbnail(): void
    {
        $this->fakeSuccessfulScrape([[
            'id' => '923321173274919',
            'pageName' => 'Nordstrom Rack',
            'body' => 'Buy online and pick up in store for free! Get up to 70% off Nike.',
            'title' => '',
            'linkDomain' => 'nordstromrack.com',
            'images' => [],
            'videos' => [
                [
                    'previewImageUrl' => 'https://scontent.example/poster.jpg',
                    'videoHdUrl' => 'https://scontent.example/ad.mp4',
                ],
            ],
            'adLibraryUrl' => 'https://www.facebook.com/ads/library/?id=923321173274919',
        ]]);

        $ad = (new PuppeteerMetaAdLibraryProvider)->search('Nike', 'US', null, 'VIDEO')->first();

        $this->assertSame('Nordstrom Rack', $ad->advertiserName);
        $this->assertSame('https://scontent.example/poster.jpg', $ad->thumbnailUrl);
        $this->assertSame(['Buy online and pick up in store for free! Get up to 70% off Nike.'], $ad->snapshot['ad_creative_bodies']);
        $this->assertSame('https://scontent.example/ad.mp4', $ad->snapshot['videos'][0]['videoHdUrl']);
    }

    public function test_search_drops_catalog_placeholders_that_still_have_no_media(): void
    {
        $this->fakeSuccessfulScrape([
            [
                'id' => '1',
                'pageName' => 'JD Sports US',
                'body' => '{{product.brand}}',
                'title' => '{{product.name}}',
                'images' => [],
                'videos' => [],
            ],
            [
                'id' => '2',
                'pageName' => 'Nike',
                'body' => 'DESIGNED TO SCULPT. ENGINEERED TO PERFORM.',
                'title' => 'Shop now',
                'images' => [['resizedUrl' => 'https://cdn.example/nike.jpg']],
                'videos' => [],
            ],
        ]);

        $results = (new PuppeteerMetaAdLibraryProvider)->search('Nike');

        $this->assertSame(['2'], $results->pluck('externalAdId')->all());
    }

    public function test_search_keeps_a_catalog_ad_when_the_listing_supplied_a_video_poster(): void
    {
        $this->fakeSuccessfulScrape([[
            'id' => '3',
            'pageName' => 'Whatnot',
            'body' => '{{product.brand}}',
            'title' => '{{product.name}}',
            'images' => [],
            'videos' => [['previewImageUrl' => 'https://scontent.example/whatnot.jpg']],
        ]]);

        $ad = (new PuppeteerMetaAdLibraryProvider)->search('Nike')->first();

        $this->assertSame('3', $ad->externalAdId);
        $this->assertSame('https://scontent.example/whatnot.jpg', $ad->thumbnailUrl);
    }

    public function test_search_by_page_does_not_scrape(): void
    {
        Process::fake();

        $results = (new PuppeteerMetaAdLibraryProvider)->searchByPage(['15087023444'], 'US', null, 'VIDEO');

        $this->assertTrue($results->isEmpty());
        Process::assertNothingRan();
    }

    public function test_begin_keyword_search_queues_the_catalog_style_scrape_job(): void
    {
        Queue::fake([ScrapeMetaAdLibraryJob::class]);

        $runId = (new PuppeteerMetaAdLibraryProvider)->beginKeywordSearch('adidas', 'IN', 'IMAGE');

        Queue::assertPushed(ScrapeMetaAdLibraryJob::class, function (ScrapeMetaAdLibraryJob $job) use ($runId): bool {
            return str_contains($job->url, 'q=adidas')
                && str_contains($job->url, 'country=IN')
                && str_contains($job->url, 'media_type=image')
                && $job->outPath === storage_path('app/private/ad-library/'.$runId.'.json');
        });
    }

    public function test_collect_run_returns_null_while_the_scrape_is_running(): void
    {
        Queue::fake([ScrapeMetaAdLibraryJob::class]);

        $runId = (new PuppeteerMetaAdLibraryProvider)->beginKeywordSearch('adidas', 'IN', 'IMAGE');

        $this->assertNull((new PuppeteerMetaAdLibraryProvider)->collectRun($runId));
        $this->assertSame('RUNNING', json_decode(File::get(storage_path('app/private/ad-library/'.$runId.'.json')), true)['status']);
    }

    public function test_collect_run_maps_ads_when_the_scrape_succeeds(): void
    {
        Queue::fake([ScrapeMetaAdLibraryJob::class]);

        $provider = new PuppeteerMetaAdLibraryProvider;
        $runId = $provider->beginKeywordSearch('adidas');

        File::put(storage_path('app/private/ad-library/'.$runId.'.json'), json_encode([
            'status' => 'SUCCEEDED',
            'ads' => [[
                'id' => '1',
                'pageName' => 'adidas',
                'body' => 'Impossible is nothing.',
                'title' => 'Shop',
                'linkDomain' => 'adidas.com',
                'images' => [['resizedUrl' => 'https://cdn.example/thumb.jpg']],
                'adLibraryUrl' => 'https://www.facebook.com/ads/library/?id=1',
            ]],
        ], JSON_THROW_ON_ERROR));

        $ad = $provider->collectRun($runId)?->first();

        $this->assertSame('1', $ad->externalAdId);
        $this->assertSame('adidas', $ad->advertiserName);
        $this->assertSame(['Impossible is nothing.'], $ad->snapshot['ad_creative_bodies']);
    }

    public function test_collect_run_throws_when_the_scrape_fails(): void
    {
        Queue::fake([ScrapeMetaAdLibraryJob::class]);

        $provider = new PuppeteerMetaAdLibraryProvider;
        $runId = $provider->beginKeywordSearch('adidas');

        File::put(storage_path('app/private/ad-library/'.$runId.'.json'), json_encode([
            'status' => 'FAILED',
            'ads' => [],
            'error' => 'Facebook Ad Library blocked automated access.',
        ], JSON_THROW_ON_ERROR));

        $this->expectException(RuntimeException::class);

        $provider->collectRun($runId);
    }

    public function test_search_throws_when_puppeteer_ad_library_is_disabled(): void
    {
        config(['creative.puppeteer_ad_library.enabled' => false]);

        $this->expectException(ProviderNotConfiguredException::class);

        (new PuppeteerMetaAdLibraryProvider)->search('adidas');
    }

    /**
     * @param  list<array<string, mixed>>  $ads
     */
    private function fakeSuccessfulScrape(array $ads = []): void
    {
        Process::preventStrayProcesses();
        Process::fake([
            '*scrape-ad-library.js*' => Process::result(json_encode([
                'status' => 'SUCCEEDED',
                'ads' => $ads !== [] ? $ads : [
                    ['id' => '1', 'pageName' => 'adidas', 'body' => 'Hi', 'images' => []],
                ],
                'error' => null,
            ], JSON_THROW_ON_ERROR)),
        ]);
    }
}
