<?php

namespace Tests\Feature;

use App\Campaigns\Publishing\MetaPublisher;
use App\Models\Asset;
use App\Models\AssetPlatformRef;
use App\Models\Campaign;
use App\Models\CampaignAd;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Client\Request as ClientRequest;
use Illuminate\Support\Facades\Http;
use Tests\SeedsVerifiedCountries;
use Tests\TestCase;

/**
 * Meta creates the campaign, the ad set and the ad paused.
 *
 * Every "it will be created paused" the chat says rests on this, and nothing
 * checked it. Google has test_every_level_google_creates_is_paused and LinkedIn
 * has LinkedInKillSwitchCoversEveryCreateTest; Meta, the platform this product
 * publishes to most, had no assertion anywhere - `grep -rn PAUSED tests/`
 * returned nothing for it. GooglePublisherTest's own docblock cites "it differed
 * from Meta, where campaign, ad set and ad are all paused" as established fact,
 * and that fact was resting on nothing.
 *
 * Found by mutation on 29 Sep: changing all three 'status' => 'PAUSED' in
 * GraphApi to 'ACTIVE' left the whole suite green at 1699 passing. A Meta
 * campaign published ACTIVE starts spending the moment it is created, and the
 * buyer has been told the opposite.
 *
 * Asserted on the request bodies that actually went out rather than on a spec
 * built here, because the defect being guarded against lives between the spec
 * and the wire.
 */
class EveryLevelMetaCreatesIsPausedTest extends TestCase
{
    use RefreshDatabase;
    use SeedsVerifiedCountries;

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

        $this->seedVerifiedCountries();

        config([
            'platforms.meta.token' => 'test-token',
            'platforms.guardrails.allowed_ad_accounts' => ['act_111'],
        ]);

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

        Http::fake([
            '*/act_111/campaigns' => Http::response(['id' => 'camp-1']),
            '*/act_111/adsets' => Http::response(['id' => 'adset-1']),
            '*/act_111/adcreatives' => Http::response(['id' => 'creative-1']),
            '*/act_111/ads' => Http::response(['id' => 'ad-1']),
            '*/act_111*' => Http::response([
                'id' => 'act_111', 'name' => 'Test Account',
                'account_status' => 1, 'currency' => 'USD',
            ]),
            '*' => Http::response(['success' => true]),
        ]);
    }

    private function publish(): void
    {
        $campaign = Campaign::create([
            'user_id' => auth()->id(),
            'name' => 'Winter Sale',
            'platform' => 'meta',
            'status' => 'draft',
            'objective' => 'OUTCOME_TRAFFIC',
            'ad_account_id' => 'act_111',
            'page_id' => 'page-1',
            'landing_url' => 'https://example.com',
            'budget' => 10,
            'budget_mode' => 'daily',
            'budget_level' => 'adset',
            'currency' => 'USD',
            'locations' => ['countries' => ['US']],
            'placements' => ['facebook'],
        ]);

        // A real asset with a platform reference, because the creative step
        // reads $ad->asset->refFor(...) and the ad is created after it. Without
        // one the publish stops at the creative and the ads endpoint is never
        // reached, which is how the first version of this test came to assert
        // nothing about the ad at all.
        $asset = Asset::create([
            'owner_type' => auth()->user()->getMorphClass(),
            'owner_id' => auth()->id(),
            'kind' => 'image',
            'source' => 'generated',
            'disk' => 'public',
            'path' => 'creatives/x.png',
            'original_name' => 'x.png',
            'mime' => 'image/png',
            'bytes' => 10,
            'checksum' => 'sum-image',
        ]);

        AssetPlatformRef::create([
            'asset_id' => $asset->id,
            'platform' => 'meta',
            'ad_account_id' => 'act_111',
            'external_hash' => 'HASH123',
        ]);

        CampaignAd::create([
            'campaign_id' => $campaign->id,
            'headline' => 'Winter Sale',
            'primary_text' => 'Everything reduced.',
            'cta' => 'SHOP_NOW',
            'asset_id' => $asset->id,
        ]);

        $created = [];

        try {
            app(MetaPublisher::class)->create($campaign, 'tester', $created);
        } catch (\Throwable) {
            // What went out is what is asserted; a later step failing is fine.
        }
    }

    /**
     * The body Meta was sent for one endpoint, matched on the whole path.
     *
     * str_contains() is wrong here and quietly so: "/act_111/adsets" and
     * "/act_111/adcreatives" both contain "/act_111/ads", so the ad assertion
     * matched the ad set request instead and passed while the ad's own status
     * was free to be anything. Caught by flipping createAd's line on its own
     * and watching nothing go red.
     */
    private function sentTo(string $path): ?array
    {
        foreach (Http::recorded() as [$request]) {
            /** @var ClientRequest $request */
            if ($request->method() !== 'POST') {
                continue;
            }

            if (str_ends_with(rtrim((string) parse_url($request->url(), PHP_URL_PATH), '/'), $path)) {
                return $request->data();
            }
        }

        return null;
    }

    public function test_the_campaign_is_created_paused(): void
    {
        $this->publish();

        $sent = $this->sentTo('/act_111/campaigns');

        $this->assertNotNull($sent, 'Nothing was sent to the campaigns endpoint.');
        $this->assertSame('PAUSED', $sent['status'] ?? null, 'the campaign was created live');
    }

    public function test_the_ad_set_is_created_paused(): void
    {
        $this->publish();

        $sent = $this->sentTo('/act_111/adsets');

        $this->assertNotNull($sent, 'Nothing was sent to the adsets endpoint.');
        $this->assertSame('PAUSED', $sent['status'] ?? null, 'the ad set was created live');
    }

    /**
     * The ad most of all.
     *
     * A paused campaign stops everything beneath it, so an ACTIVE ad under a
     * paused campaign serves nothing today. It becomes the thing that starts
     * spending the instant anyone unpauses the campaign expecting to review it
     * first, which is exactly what the two stops above are for.
     */
    public function test_the_ad_is_created_paused(): void
    {
        $this->publish();

        $sent = $this->sentTo('/act_111/ads');

        $this->assertNotNull($sent, 'Nothing was sent to the ads endpoint.');
        $this->assertSame('PAUSED', $sent['status'] ?? null, 'the ad was created live');
    }
}
