<?php

namespace Tests\Feature;

use App\Services\LinkedIn\LinkedInRestApi;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;

/**
 * With publishing off, nothing LinkedIn creates is live.
 *
 * The guard looked at one key, `status`, and only when it was already present.
 * Two of the three things this client creates do not use that key: a creative
 * carries `intendedStatus`, and a Direct Sponsored Content post carries
 * `lifecycleState` and `adContext.dscStatus`.
 *
 * So LINKEDIN_PUBLISH_ENABLED=false paused the campaign and let the creative go
 * out ACTIVE and the post go out PUBLISHED and PUBLIC on the company Page. The
 * setting that reads as "nothing goes live" did not stop the most public thing
 * this client does.
 *
 * A switch that covers most of the ways to go live is not a switch, so each
 * path is asserted separately rather than trusting one shared helper.
 */
class LinkedInKillSwitchCoversEveryCreateTest extends TestCase
{
    use RefreshDatabase;

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

        config([
            'platforms.linkedin.token' => 'test-token',
            'platforms.linkedin.publish_enabled' => false,
            'platforms.guardrails.allowed_ad_accounts' => ['556447014'],
        ]);

        Http::fake(['https://api.linkedin.com/*' => Http::response([], 201, ['x-restli-id' => 'urn:li:thing:1'])]);
    }

    /** @return array<string, mixed> the body that went to LinkedIn */
    private function sentBody(string $contains): array
    {
        foreach (Http::recorded() as [$request]) {
            /** @var Request $request */
            if (str_contains($request->url(), $contains)) {
                return $request->data();
            }
        }

        return [];
    }

    /**
     * DRAFT, and PAUSED would be rejected outright.
     *
     * This asserted PAUSED and passed, because Http::fake accepts anything. The
     * real API does not: "/CampaignGroup/status cannot be changed from null to
     * PAUSED", because a group is created DRAFT or ACTIVE and only moves to
     * PAUSED afterwards. Every LinkedIn publish failed on it, and only driving
     * arb-dev end to end showed it.
     *
     * DRAFT is the stricter of the two anyway: a paused group can be resumed, a
     * draft one has never been live.
     */
    public function test_a_campaign_group_is_created_as_a_draft(): void
    {
        app(LinkedInRestApi::class)->createCampaignGroup('556447014', ['name' => 'Q4']);

        $this->assertSame('DRAFT', $this->sentBody('adCampaignGroups')['status'] ?? null);
    }

    /** Never PAUSED on creation, which is the shape LinkedIn refuses. */
    public function test_a_campaign_group_is_never_created_paused(): void
    {
        app(LinkedInRestApi::class)->createCampaignGroup('556447014', ['name' => 'Q4']);

        $this->assertNotSame('PAUSED', $this->sentBody('adCampaignGroups')['status'] ?? null);
    }

    /** Even when the caller explicitly asks for ACTIVE. */
    public function test_a_campaign_group_asked_for_active_is_still_held_back(): void
    {
        app(LinkedInRestApi::class)->createCampaignGroup('556447014', ['name' => 'Q4', 'status' => 'ACTIVE']);

        $this->assertSame('DRAFT', $this->sentBody('adCampaignGroups')['status'] ?? null);
    }

    public function test_a_campaign_is_created_paused(): void
    {
        app(LinkedInRestApi::class)->createCampaign('556447014', ['name' => 'Q4', 'status' => 'ACTIVE']);

        $this->assertSame('PAUSED', $this->sentBody('adCampaigns')['status'] ?? null);
    }

    /** The creative's own switch is intendedStatus, which was never guarded. */
    public function test_a_creative_is_not_created_active(): void
    {
        app(LinkedInRestApi::class)->createCreative('556447014', ['campaign' => 'urn:li:sponsoredCampaign:1']);

        $this->assertSame('DRAFT', $this->sentBody('creatives')['intendedStatus'] ?? null);
    }

    /**
     * The post is the one that reaches the public. lifecycleState PUBLISHED
     * with visibility PUBLIC puts it on the company Page under its own name.
     */
    public function test_a_sponsored_post_is_not_published(): void
    {
        app(LinkedInRestApi::class)->createPost([
            'author' => 'urn:li:organization:106065219',
            'commentary' => 'Body',
            'visibility' => 'PUBLIC',
            'lifecycleState' => 'PUBLISHED',
            'adContext' => ['dscStatus' => 'ACTIVE', 'dscName' => 'Ad'],
        ], '556447014');

        $body = $this->sentBody('rest/posts');

        $this->assertSame('DRAFT', $body['lifecycleState'] ?? null);
        $this->assertSame('DRAFT', $body['adContext']['dscStatus'] ?? null);
    }

    /** And with publishing on, the caller's intent is respected. */
    public function test_nothing_is_downgraded_once_publishing_is_enabled(): void
    {
        config(['platforms.linkedin.publish_enabled' => true]);

        app(LinkedInRestApi::class)->createCreative('556447014', ['campaign' => 'urn:li:sponsoredCampaign:1']);

        $this->assertSame('ACTIVE', $this->sentBody('creatives')['intendedStatus'] ?? null);
    }
}
