<?php

namespace Tests\Feature;

use App\Models\CompetitorAdReference;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;

/**
 * The competitor-ad table cannot hand our access token back out.
 *
 * `MetaAdLibraryProvider` completed Meta's bare `&access_token` with
 * `services.meta.access_token`, and the finished URL was saved into
 * `competitor_ad_references.snapshot` and rendered as an `<a href>` in the
 * creative wizard. So rows written before that was fixed hold a live credential,
 * and the page that reads them puts it in the DOM.
 *
 * Fixing the provider stops new rows. It does nothing about the ones already
 * there, and those are the dangerous ones, so the model strips on read as well -
 * no data migration, and a provider that regresses cannot put one back either.
 *
 * The column is deliberately left as it is on disk. Rewriting it would hide how
 * many rows were affected, which is what decides whether the token needs
 * rotating.
 */
class AStoredAdReferenceCannotCarryOurTokenTest extends TestCase
{
    use RefreshDatabase;

    private const LEAKED = 'https://www.facebook.com/ads/archive/render_ad/?id=987&access_token=EAABsecretlivetoken';

    private function reference(string $url): CompetitorAdReference
    {
        return CompetitorAdReference::create([
            'user_id' => User::factory()->create()->id,
            'platform' => 'facebook',
            'external_ad_id' => '987',
            'search_query' => 'nike',
            'snapshot' => ['id' => '987', 'page_name' => 'Acme', 'ad_snapshot_url' => $url],
        ]);
    }

    /** The defect: a row written before the fix must not read back a token. */
    public function test_a_row_written_with_a_token_reads_back_without_one(): void
    {
        $id = $this->reference('placeholder')->id;

        // Written behind the model, exactly as the old provider left it.
        DB::table('competitor_ad_references')->where('id', $id)->update([
            'snapshot' => json_encode(['id' => '987', 'ad_snapshot_url' => self::LEAKED]),
        ]);

        $snapshot = CompetitorAdReference::find($id)->snapshot;

        $this->assertStringNotContainsString('access_token', $snapshot['ad_snapshot_url']);
        $this->assertStringNotContainsString('EAABsecretlivetoken', json_encode($snapshot));
        $this->assertSame('https://www.facebook.com/ads/archive/render_ad/?id=987', $snapshot['ad_snapshot_url']);
    }

    /** And a token cannot be written through the model in the first place. */
    public function test_a_token_is_stripped_on_the_way_in(): void
    {
        $reference = $this->reference(self::LEAKED);

        $stored = DB::table('competitor_ad_references')->where('id', $reference->id)->value('snapshot');

        $this->assertStringNotContainsString('access_token', (string) $stored, 'the token reached the column');
    }

    /** The Apify and Puppeteer providers use a different field; it is covered too. */
    public function test_the_ad_library_url_field_is_also_stripped(): void
    {
        $reference = CompetitorAdReference::create([
            'user_id' => User::factory()->create()->id,
            'platform' => 'facebook',
            'external_ad_id' => '654',
            'search_query' => 'nike',
            'snapshot' => ['adLibraryUrl' => self::LEAKED],
        ]);

        $this->assertStringNotContainsString('access_token', $reference->fresh()->snapshot['adLibraryUrl']);
    }

    /** Everything else in the snapshot survives untouched. */
    public function test_the_rest_of_the_snapshot_is_unchanged(): void
    {
        $reference = $this->reference('https://www.facebook.com/ads/library/?id=987');

        $snapshot = $reference->fresh()->snapshot;

        $this->assertSame('987', $snapshot['id']);
        $this->assertSame('Acme', $snapshot['page_name']);
        $this->assertSame('https://www.facebook.com/ads/library/?id=987', $snapshot['ad_snapshot_url']);
    }

    /** A row with no snapshot at all is not turned into an error. */
    public function test_an_empty_snapshot_stays_empty(): void
    {
        $reference = CompetitorAdReference::create([
            'user_id' => User::factory()->create()->id,
            'platform' => 'facebook',
            'external_ad_id' => '321',
            'search_query' => 'nike',
        ]);

        $this->assertNull($reference->fresh()->snapshot);
    }
}
