<?php

namespace Tests\Feature;

use App\Models\GoogleAdsConnection;
use App\Services\GoogleAdsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;

/**
 * A campaign told "mobile only" does not go out reaching desktops.
 *
 * set_targeting has collected devices since the flow existed, the panel shows
 * them, and GooglePublisher never put them in the payload. The buyer picked
 * mobile only, saw mobile only on screen, and the campaign reached every device
 * Google has.
 *
 * Google does not target devices, it un-targets them: a campaign reaches
 * desktop, mobile and tablet from the moment it exists, and the only way to
 * narrow that is a bid modifier of 0 on the ones not wanted. Checked against
 * the live account rather than assumed, because the obvious shape is wrong:
 *
 *   {"device": {"type": "TABLET"}, "negative": true}   -> IMMUTABLE_FIELD
 *   {"device": {"type": "TABLET"}, "bidModifier": 0}   -> accepted
 */
class DevicesReachGoogleTest extends TestCase
{
    use RefreshDatabase;

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

        config([
            'services.google_ads.refresh_token' => 'a-refresh-token',
            'services.google_ads.customer_id' => '3460855874',
            'services.google_ads.developer_token' => 'dev-token',
            'platforms.guardrails.allowed_ad_accounts' => ['3460855874'],
        ]);

        Http::fake([
            'oauth2.googleapis.com/*' => Http::response(['access_token' => 'token', 'expires_in' => 3600]),
            'googleads.googleapis.com/*' => Http::response([
                'results' => [['resourceName' => 'customers/3460855874/campaigns/1']],
            ]),
        ]);
    }

    /**
     * The device criteria a run would send.
     *
     * @param  list<string>  $devices
     * @return list<array<string,mixed>>
     */
    private function criteriaFor(array $devices): array
    {
        $created = [];

        try {
            app(GoogleAdsService::class)->createPausedCampaign(GoogleAdsConnection::shared(), [
                'campaign_type' => 'search',
                'name' => 'Devices',
                'ad_group_name' => 'Devices - ad group',
                'daily_budget' => 10.0,
                'bidding_strategy' => 'maximize_clicks',
                'final_url' => 'https://iblockads.org/',
                'locations' => ['US'],
                'networks' => ['search'],
                'devices' => $devices,
                'keywords' => 'adblocker',
                'headlines' => ['One', 'Two', 'Three'],
                'descriptions' => ['First line.', 'Second line.'],
            ], $created);
        } catch (\Throwable) {
            // The fake does not model every response; the payload is the point.
        }

        foreach (Http::recorded() as [$request]) {
            /** @var Request $request */
            if (str_contains($request->url(), 'campaignCriteria:mutate')) {
                return collect($request->data()['operations'] ?? [])
                    ->map(fn (array $op): array => $op['create'] ?? [])
                    ->filter(fn (array $c): bool => isset($c['device']))
                    ->values()
                    ->all();
            }
        }

        return [];
    }

    /** @param list<array<string,mixed>> $criteria */
    private function excluded(array $criteria): array
    {
        return collect($criteria)
            ->filter(fn (array $c): bool => ($c['bidModifier'] ?? null) === 0)
            ->map(fn (array $c): string => $c['device']['type'])
            ->sort()
            ->values()
            ->all();
    }

    public function test_mobile_only_excludes_desktop(): void
    {
        $this->assertSame(['DESKTOP'], $this->excluded($this->criteriaFor(['mobile'])));
    }

    /** Tablets go with mobile, so asking for desktop excludes both. */
    public function test_desktop_only_excludes_mobile_and_tablet(): void
    {
        $this->assertSame(['MOBILE', 'TABLET'], $this->excluded($this->criteriaFor(['desktop'])));
    }

    /** Both chosen is Google's own default, so nothing is sent. */
    public function test_both_devices_exclude_nothing(): void
    {
        $this->assertSame([], $this->criteriaFor(['mobile', 'desktop']));
    }

    /** And nobody choosing is not the same as choosing nothing. */
    public function test_no_answer_excludes_nothing(): void
    {
        $this->assertSame([], $this->criteriaFor([]));
    }

    /**
     * Never excluded from everything.
     *
     * A value that matches neither word would otherwise exclude all three and
     * build a campaign that cannot reach anybody.
     */
    public function test_an_unrecognised_device_does_not_silence_the_campaign(): void
    {
        $this->assertSame([], $this->criteriaFor(['smart_fridge']));
    }

    /** Exclusion is a bid modifier of 0; "negative" is refused by Google. */
    public function test_exclusion_is_sent_as_a_bid_modifier_not_as_negative(): void
    {
        $criteria = $this->criteriaFor(['mobile']);

        $this->assertNotEmpty($criteria);

        foreach ($criteria as $criterion) {
            $this->assertArrayNotHasKey('negative', $criterion, 'Google refuses this with IMMUTABLE_FIELD.');
            $this->assertSame(0, $criterion['bidModifier'] ?? null);
        }
    }
}
