<?php

namespace Tests\Feature;

use App\Models\ApiCall;
use App\Models\GoogleAdsConnection;
use App\Models\PlatformConnection;
use App\Models\User;
use App\Services\GoogleAdsService;
use App\Services\Meta\MetaException;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;

/**
 * Google reaches the API through the same guardrails Meta does.
 *
 * Until step 8 of the multi-platform plan, GoogleAdsService called Http::
 * directly. Three things followed: the account allow list did not apply to
 * Google, the hourly call budget did not count a Google call, and api_calls
 * held no record that one had been made. The audit trail described a product
 * that only talked to Meta, which stopped being true the moment a campaign
 * could run on both.
 */
class GoogleCallsAreGuardedTest extends TestCase
{
    use RefreshDatabase;

    private function connection(): GoogleAdsConnection
    {
        return GoogleAdsConnection::create([
            'user_id' => User::factory()->create()->id,
            'customer_id' => '123-456-7890',
            'manager_customer_id' => '987-654-3210',
            'available_customer_ids' => ['123-456-7890', '987-654-3210'],
            'refresh_token' => 'refresh-token',
        ]);
    }

    private function fakeGoogle(): void
    {
        Http::fake([
            'oauth2.googleapis.com/*' => Http::response(['access_token' => 'token', 'expires_in' => 3600]),
            'googleads.googleapis.com/*' => Http::response([
                ['results' => []],
            ]),
        ]);
    }

    /** A call to an account nobody allowed must not reach Google at all. */
    public function test_a_google_account_outside_the_allow_list_is_refused(): void
    {
        config(['platforms.guardrails.allowed_ad_accounts' => ['act_777']]);
        $this->fakeGoogle();

        $this->expectException(MetaException::class);

        app(GoogleAdsService::class)->conversionActionOptions($this->connection());
    }

    /**
     * And the same id written the way Google writes it is allowed.
     *
     * The check used to force Meta's `act_` prefix onto every id, so a Google
     * customer was compared as act_1234567890 and could never match.
     */
    public function test_a_google_account_on_the_allow_list_is_permitted(): void
    {
        config(['platforms.guardrails.allowed_ad_accounts' => ['1234567890']]);
        $this->fakeGoogle();

        app(GoogleAdsService::class)->conversionActionOptions($this->connection());

        $this->assertTrue(true, 'The call was not refused.');
    }

    /** Hyphens are how Google writes a customer id in its own UI. */
    public function test_the_allow_list_ignores_the_hyphens_google_writes_ids_with(): void
    {
        config(['platforms.guardrails.allowed_ad_accounts' => ['1234567890']]);
        $this->fakeGoogle();

        app(GoogleAdsService::class)->conversionActionOptions($this->connection());

        $this->assertDatabaseHas('api_calls', ['ad_account_id' => '1234567890']);
    }

    /** Every Google call leaves a row, the same as every Meta call. */
    public function test_a_google_call_is_recorded_in_the_audit_trail(): void
    {
        config(['platforms.guardrails.allowed_ad_accounts' => ['1234567890']]);
        $this->fakeGoogle();

        app(GoogleAdsService::class)->conversionActionOptions($this->connection());

        $call = ApiCall::query()->latest('id')->first();

        $this->assertNotNull($call, 'A Google call left no audit row.');
        $this->assertSame('google_ads', $call->transport);
        $this->assertSame('1234567890', $call->ad_account_id);
        $this->assertFalse((bool) $call->mutating);
        $this->assertTrue((bool) $call->ok);
    }

    /**
     * The refresh token must never reach the audit trail.
     *
     * The OAuth exchange is deliberately outside the logger: it is not an Ads
     * API call, and its payload is a long-lived credential.
     */
    public function test_the_oauth_token_exchange_is_not_recorded(): void
    {
        config(['platforms.guardrails.allowed_ad_accounts' => ['1234567890']]);
        $this->fakeGoogle();

        app(GoogleAdsService::class)->conversionActionOptions($this->connection());

        foreach (ApiCall::all() as $call) {
            $this->assertStringNotContainsString('oauth2', (string) $call->endpoint);
            $this->assertStringNotContainsString(
                'refresh-token',
                json_encode($call->request),
                'A refresh token was written to the audit trail.',
            );
        }
    }

    public function test_google_api_uses_shared_database_credentials_without_env_configuration(): void
    {
        config([
            'platforms.guardrails.allowed_ad_accounts' => ['1234567890'],
            'services.google_ads.developer_token' => null,
            'services.google_ads.oauth_client_id' => null,
            'services.google_ads.oauth_client_secret' => null,
            'services.google_ads.refresh_token' => null,
            'services.google_ads.customer_id' => null,
        ]);
        PlatformConnection::factory()->google('1234567890', 'db-refresh-token')->create([
            'user_id' => null,
            'platform_data' => ['manager_customer_id' => '9876543210', 'client_id' => 'db-client-id'],
            'platform_secrets' => ['developer_token' => 'db-developer-token', 'client_secret' => 'db-client-secret'],
        ]);
        $this->fakeGoogle();

        app(GoogleAdsService::class)->conversionActionOptions(GoogleAdsConnection::shared());

        Http::assertSent(fn ($request): bool => str_contains($request->url(), 'oauth2.googleapis.com')
            && $request['client_id'] === 'db-client-id'
            && $request['client_secret'] === 'db-client-secret'
            && $request['refresh_token'] === 'db-refresh-token');
        Http::assertSent(fn ($request): bool => str_contains($request->url(), 'googleads.googleapis.com')
            && $request->hasHeader('developer-token', 'db-developer-token'));
    }
}
