<?php

namespace Tests\Unit\Services\AI;

use App\Services\AI\MetaAdLibraryProvider;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;

class MetaAdLibraryProviderTest extends TestCase
{
    public function test_search_without_a_country_defaults_to_the_united_states(): void
    {
        $this->fakeArchiveResponse();

        (new MetaAdLibraryProvider)->search('Nike');

        Http::assertSent(fn ($request): bool => str_contains($request->url(), 'ads_archive')
            && $request['ad_reached_countries'] === '["US"]'
            && $request['search_terms'] === 'Nike'
            && $request['media_type'] === 'IMAGE');
    }

    public function test_search_sends_video_media_type_when_requested(): void
    {
        $this->fakeArchiveResponse();

        (new MetaAdLibraryProvider)->search('Nike', 'US', null, 'VIDEO');

        Http::assertSent(fn ($request): bool => str_contains($request->url(), 'ads_archive')
            && $request['media_type'] === 'VIDEO');
    }

    public function test_search_sends_image_media_type_when_requested(): void
    {
        $this->fakeArchiveResponse();

        (new MetaAdLibraryProvider)->search('Nike', 'US', null, 'IMAGE');

        Http::assertSent(fn ($request): bool => str_contains($request->url(), 'ads_archive')
            && $request['media_type'] === 'IMAGE');
    }

    public function test_search_by_page_sends_search_page_ids_instead_of_search_terms(): void
    {
        $this->fakeArchiveResponse();

        (new MetaAdLibraryProvider)->searchByPage(['15087023444'], 'US', null, 'VIDEO');

        Http::assertSent(fn ($request): bool => str_contains($request->url(), 'ads_archive')
            && $request['search_page_ids'] === '["15087023444"]'
            && $request['media_type'] === 'VIDEO'
            && $request['ad_reached_countries'] === '["US"]'
            && ! isset($request['search_terms']));
    }

    public function test_search_by_page_caps_page_ids_at_ten(): void
    {
        $this->fakeArchiveResponse();

        $pageIds = array_map(fn (int $id): string => (string) $id, range(1, 12));

        (new MetaAdLibraryProvider)->searchByPage($pageIds);

        Http::assertSent(function ($request): bool {
            $sent = json_decode((string) $request['search_page_ids'], true);

            return str_contains($request->url(), 'ads_archive')
                && is_array($sent)
                && count($sent) === 10;
        });
    }

    public function test_search_by_page_returns_no_ads_when_no_numeric_ids_are_given(): void
    {
        Http::preventStrayRequests();

        $results = (new MetaAdLibraryProvider)->searchByPage(['nike', '']);

        $this->assertSame([], $results->all());
    }

    public function test_search_uses_an_explicit_country_code(): void
    {
        $this->fakeArchiveResponse();

        (new MetaAdLibraryProvider)->search('Nike', 'IN');

        Http::assertSent(fn ($request): bool => str_contains($request->url(), 'ads_archive')
            && $request['ad_reached_countries'] === '["IN"]');
    }

    public function test_search_uses_the_country_from_the_request_when_one_is_supplied(): void
    {
        $this->fakeArchiveResponse();

        request()->merge(['country' => 'GB']);

        (new MetaAdLibraryProvider)->search('Nike');

        Http::assertSent(fn ($request): bool => str_contains($request->url(), 'ads_archive')
            && $request['ad_reached_countries'] === '["GB"]');
    }

    public function test_search_returns_ads_newest_first_by_delivery_start_time(): void
    {
        $this->fakeArchiveResponse([
            $this->archiveAd('old', '2024-01-01T00:00:00+0000'),
            $this->archiveAd('new', '2026-09-01T00:00:00+0000'),
            $this->archiveAd('mid', '2025-06-01T00:00:00+0000'),
        ]);

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

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

    public function test_pagination_forwards_the_after_cursor_with_the_selected_country(): void
    {
        $this->fakeArchiveResponse();

        (new MetaAdLibraryProvider)->search('Nike', 'CA', 'CURSOR123');

        Http::assertSent(fn ($request): bool => str_contains($request->url(), 'ads_archive')
            && $request['ad_reached_countries'] === '["CA"]'
            && $request['after'] === 'CURSOR123'
            && $request['search_terms'] === 'Nike');
    }

    public function test_pagination_from_the_request_keeps_country_and_newest_first_ordering(): void
    {
        request()->merge([
            'country' => 'AU',
            'after' => 'PAGE2',
        ]);

        $this->fakeArchiveResponse([
            $this->archiveAd('older', '2025-01-01T00:00:00+0000'),
            $this->archiveAd('newer', '2026-01-01T00:00:00+0000'),
        ]);

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

        Http::assertSent(fn ($request): bool => str_contains($request->url(), 'ads_archive')
            && $request['ad_reached_countries'] === '["AU"]'
            && $request['after'] === 'PAGE2');

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

    public function test_search_uses_the_page_picture_as_the_thumbnail_url(): void
    {
        $this->fakeArchiveResponse([
            $this->archiveAd('987', '2026-01-01T00:00:00+0000'),
        ]);

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

        $this->assertSame('987', $ad->externalAdId);
        $this->assertSame('https://graph.facebook.com/v26.0/123/picture?type=large', $ad->thumbnailUrl);
        $this->assertSame('987', $ad->snapshot['id']);

        // The public Ad Library permalink, not the render_ad URL this used to
        // pass through. render_ad needs a token to show anything, which is
        // exactly why this file used to attach one; see the test below.
        $this->assertSame(
            'https://www.facebook.com/ads/library/?id=987',
            $ad->snapshot['ad_snapshot_url'],
        );
    }

    /**
     * The access token is never attached to a URL we store.
     *
     * Meta returns `ad_snapshot_url` ending in a bare `&access_token`, and this
     * completed it with `services.meta.access_token` - the token every Meta write
     * in the product uses. The result was stored in
     * `CompetitorAdReference.snapshot`, a plain array column, and rendered as an
     * `<a href>` in the creative wizard, so the token reached the database in the
     * clear and the page source of anyone who opened the picker.
     *
     * No test reached that line, because the fixture above supplies a
     * `render_ad` URL with no `&access_token` on the end. This one uses the shape
     * Meta actually returns.
     */
    public function test_the_access_token_is_never_attached_to_the_snapshot_url(): void
    {
        $ad = $this->archiveAd('987', '2026-01-01T00:00:00+0000');
        $ad['ad_snapshot_url'] = 'https://www.facebook.com/ads/archive/render_ad/?id=987&access_token';

        $this->fakeArchiveResponse([$ad]);

        $snapshot = (new MetaAdLibraryProvider)->search('Nike')->first()->snapshot;

        $this->assertStringNotContainsString('access_token', $snapshot['ad_snapshot_url']);
        $this->assertStringNotContainsString('test-token', json_encode($snapshot));
    }

    /** And one that already carries a token has it removed, not passed on. */
    public function test_a_token_already_in_the_url_is_stripped(): void
    {
        $ad = $this->archiveAd('', '2026-01-01T00:00:00+0000');
        $ad['id'] = '';
        $ad['ad_snapshot_url'] = 'https://www.facebook.com/ads/archive/render_ad/?id=987&access_token=EAAsecret';

        $this->fakeArchiveResponse([$ad]);

        $url = (new MetaAdLibraryProvider)->search('Nike')->first()->snapshot['ad_snapshot_url'];

        $this->assertSame('https://www.facebook.com/ads/archive/render_ad/?id=987', $url);
    }

    /**
     * @param  list<array<string, mixed>>  $ads
     */
    private function fakeArchiveResponse(array $ads = []): void
    {
        config(['services.meta.access_token' => 'test-token']);

        Http::preventStrayRequests();
        Http::fake([
            'https://graph.facebook.com/v26.0/ads_archive*' => Http::response([
                'data' => $ads !== [] ? $ads : [
                    $this->archiveAd('1', '2026-01-01T00:00:00+0000'),
                ],
            ]),
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    private function archiveAd(string $id, string $startedAt): array
    {
        return [
            'id' => $id,
            'page_id' => '123',
            'page_name' => 'Acme',
            'ad_creative_bodies' => ['Copy'],
            'ad_creative_link_titles' => ['Title'],
            'ad_creative_link_captions' => ['Caption'],
            'ad_snapshot_url' => 'https://www.facebook.com/ads/archive/render_ad/?id='.$id,
            'publisher_platforms' => ['facebook'],
            'ad_delivery_start_time' => $startedAt,
        ];
    }
}
