<?php

namespace Tests\Feature;

use App\Agent\Tools\Campaign\OpenCampaign;
use App\Agent\Workspace;
use App\Campaigns\CampaignImporter;
use App\Campaigns\PendingChanges;
use App\Campaigns\PublishGate;
use App\Campaigns\Publishing\GooglePublisher;
use App\Campaigns\Targeting\TargetingSelections;
use App\Models\Campaign;
use App\Models\GoogleAdsConnection;
use App\Models\User;
use App\Services\GoogleAdsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use Laravel\Ai\Tools\Request as ToolRequest;
use RuntimeException;
use Tests\TestCase;

class GoogleCampaignImportTest extends TestCase
{
    use RefreshDatabase;

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

        config([
            'platforms.guardrails.allowed_ad_accounts' => ['google:1234567890'],
            'services.google_ads.refresh_token' => 'refresh-token',
            'services.google_ads.customer_id' => '123-456-7890',
        ]);
    }

    private function fakeGoogle(bool $invalidLocation = false, bool $unsupportedGeography = false, int $budgetReferenceCount = 1): void
    {
        Http::fake(function (Request $request) use ($invalidLocation, $unsupportedGeography, $budgetReferenceCount) {
            if (str_contains($request->url(), 'oauth2.googleapis.com')) {
                return Http::response(['access_token' => 'token', 'expires_in' => 3600]);
            }

            $query = (string) ($request->data()['query'] ?? '');
            if (str_contains($query, "'PROXIMITY'")) {
                return Http::response([['results' => $unsupportedGeography
                    ? [['campaignCriterion' => ['type' => 'PROXIMITY']]]
                    : []]]);
            }

            if (str_contains($query, 'FROM campaign_criterion')) {
                return Http::response([['results' => [
                    ['campaignCriterion' => [
                        'resourceName' => 'customers/1234567890/campaignCriteria/222~1',
                        'location' => ['geoTargetConstant' => 'geoTargetConstants/2840'],
                        'negative' => false,
                    ], 'geoTargetConstant' => ['countryCode' => 'US', 'targetType' => 'Country', 'canonicalName' => 'United States']],
                    ['campaignCriterion' => [
                        'resourceName' => 'customers/1234567890/campaignCriteria/222~2',
                        'location' => ['geoTargetConstant' => $invalidLocation ? null : 'geoTargetConstants/1023191'],
                        'negative' => false,
                    ], 'geoTargetConstant' => ['countryCode' => 'US', 'targetType' => 'City', 'canonicalName' => 'San Francisco, California, United States']],
                    ['campaignCriterion' => [
                        'resourceName' => 'customers/1234567890/campaignCriteria/222~3',
                        'location' => ['geoTargetConstant' => 'geoTargetConstants/21137'],
                        'negative' => true,
                    ], 'geoTargetConstant' => ['countryCode' => 'US', 'targetType' => 'State', 'canonicalName' => 'California, United States']],
                ]]]);
            }

            return Http::response([['results' => [[
                'campaign' => ['id' => '222', 'name' => 'Imported Search', 'status' => 'PAUSED', 'advertisingChannelType' => 'SEARCH'],
                'campaignBudget' => ['resourceName' => 'customers/1234567890/campaignBudgets/333', 'referenceCount' => config('testing.google_budget_reference_count', $budgetReferenceCount), 'amountMicros' => '25000000'],
            ]]]]);
        });
    }

    public function test_google_import_keeps_exact_included_and_excluded_geo_criteria_in_its_baseline(): void
    {
        $this->fakeGoogle();
        $owner = User::factory()->create();

        $campaign = app(CampaignImporter::class)->adoptGoogle('222', $owner);

        $this->assertSame('google', $campaign->platform);
        $this->assertSame('search', $campaign->campaign_type);
        $this->assertSame('333', $campaign->external_ids['budget_id']);
        $this->assertSame(['US'], $campaign->locations['countries']);
        $this->assertCount(3, $campaign->locations['google_location_criteria']);
        $this->assertSame('geoTargetConstants/1023191', $campaign->locations['google_location_criteria'][1]['geo_target']);
        $this->assertTrue($campaign->locations['google_location_criteria'][2]['negative']);
        $this->assertSame($campaign->locations, $campaign->published_state['locations']);
        $this->assertSame([], app(PendingChanges::class)->requiringRebuild($campaign));

        $again = app(CampaignImporter::class)->adoptGoogle('222', $owner);
        $this->assertSame($campaign->id, $again->id);
        $this->assertSame(1, Campaign::query()->count());
    }

    public function test_an_unreadable_google_location_aborts_import_without_creating_a_campaign(): void
    {
        $this->fakeGoogle(invalidLocation: true);

        try {
            app(CampaignImporter::class)->adoptGoogle('222', User::factory()->create());
            $this->fail('Import should have refused the incomplete location criterion.');
        } catch (RuntimeException $exception) {
            $this->assertStringContainsString('location criterion', $exception->getMessage());
        }

        $this->assertSame(0, Campaign::query()->count());
    }

    public function test_imported_google_places_and_exclusions_are_visible_in_review(): void
    {
        $this->fakeGoogle();
        $campaign = app(CampaignImporter::class)->adoptGoogle('222', User::factory()->create());

        $html = view('partials.campaign-panel', [
            'campaign' => $campaign,
            'progress' => [],
            'nextStep' => null,
            'blocking' => [],
            'sources' => [],
            'pending' => [],
            'needsRebuild' => [],
            'campaigns' => collect([$campaign]),
            'assets' => collect(),
        ])->render();

        $this->assertStringContainsString('San Francisco, California, United States', $html);
        $this->assertStringContainsString('Imported Google excluded state: California, United States', $html);
    }

    public function test_google_import_refuses_unrepresented_radius_targeting(): void
    {
        $this->fakeGoogle(unsupportedGeography: true);

        try {
            app(CampaignImporter::class)->adoptGoogle('222', User::factory()->create());
            $this->fail('Import should have refused unsupported geographic targeting.');
        } catch (RuntimeException $exception) {
            $this->assertStringContainsString('proximity or location-group', $exception->getMessage());
        }

        $this->assertSame(0, Campaign::query()->count());
    }

    public function test_imported_shared_google_budget_cannot_be_edited_as_a_single_campaign_budget(): void
    {
        $this->fakeGoogle(budgetReferenceCount: 2);
        $campaign = app(CampaignImporter::class)->adoptGoogle('222', User::factory()->create());

        $this->assertSame(2, $campaign->external_ids['budget_reference_count']);
        $this->expectExceptionMessage('budget may be shared by multiple campaigns');

        app(GooglePublisher::class)->update($campaign, ['budget' => ['from' => '25', 'to' => '30']], 'tester');
    }

    public function test_unrelated_google_name_edit_does_not_replace_imported_location_criteria(): void
    {
        $this->fakeGoogle();
        $campaign = app(CampaignImporter::class)->adoptGoogle('222', User::factory()->create());
        $importedCriteria = $campaign->locations['google_location_criteria'];

        app(GooglePublisher::class)->update($campaign, ['name' => ['from' => 'Imported Search', 'to' => 'Renamed Search']], 'tester');

        Http::assertSent(fn (Request $request): bool => str_contains($request->url(), 'campaigns:mutate'));
        Http::assertNotSent(fn (Request $request): bool => str_contains($request->url(), 'campaignCriteria:mutate'));
        $this->assertSame($importedCriteria, $campaign->fresh()->locations['google_location_criteria']);
    }

    public function test_imported_google_budget_is_rechecked_before_any_live_edit(): void
    {
        $this->fakeGoogle();
        $campaign = app(CampaignImporter::class)->adoptGoogle('222', User::factory()->create());
        config(['testing.google_budget_reference_count' => 2]);
        $this->assertSame(2, config('testing.google_budget_reference_count'));
        $this->assertSame(2, app(GoogleAdsService::class)->readCampaignProfile(GoogleAdsConnection::shared(), '222', fresh: true)['budget_reference_count']);
        $callsBefore = Http::recorded()->count();

        try {
            app(GooglePublisher::class)->update($campaign, [
                'name' => ['from' => 'Imported Search', 'to' => 'Renamed Search'],
                'budget' => ['from' => '25', 'to' => '30'],
            ], 'tester');
            $this->fail('The now-shared budget should have blocked the edit.');
        } catch (RuntimeException $exception) {
            $this->assertStringContainsString('ownership changed since import', $exception->getMessage());
        }

        $this->assertGreaterThan($callsBefore, Http::recorded()->count());
        Http::assertNotSent(fn (Request $request): bool => str_contains($request->url(), ':mutate'));
    }

    public function test_imported_city_only_google_campaign_is_not_mistaken_for_worldwide_targeting(): void
    {
        $campaign = Campaign::query()->create([
            'user_id' => User::factory()->create()->id,
            'name' => 'Imported City',
            'platform' => 'google',
            'status' => 'published',
            'ad_account_id' => '1234567890',
            'external_campaign_id' => '222',
            'locations' => ['countries' => [], 'google_location_criteria' => [[
                'criterion' => 'customers/1234567890/campaignCriteria/222~2',
                'geo_target' => 'geoTargetConstants/1023191',
                'country_code' => 'US',
                'target_type' => 'City',
                'name' => 'San Francisco, California, United States',
                'negative' => false,
            ]]],
        ]);

        $this->assertNotContains('target country not set', app(PublishGate::class)->soundness($campaign));
    }

    public function test_imported_google_criteria_cannot_be_republished_as_a_broader_draft(): void
    {
        $this->fakeGoogle();
        $campaign = app(CampaignImporter::class)->adoptGoogle('222', User::factory()->create());
        $campaign->update(['external_campaign_id' => null, 'status' => 'draft']);

        $this->expectExceptionMessage('Imported Google location criteria must be reselected');

        app(TargetingSelections::class)->googleGeoTargets($campaign->fresh());
    }

    public function test_google_import_checks_the_account_allowlist_before_reading(): void
    {
        config(['platforms.guardrails.allowed_ad_accounts' => []]);
        Http::fake();

        $this->expectExceptionMessage('No ad accounts are allowed');

        try {
            app(CampaignImporter::class)->adoptGoogle('222', User::factory()->create());
        } finally {
            Http::assertNothingSent();
        }
    }

    public function test_open_campaign_tool_can_adopt_google_by_explicit_platform_and_id(): void
    {
        $this->fakeGoogle();
        $owner = User::factory()->create();
        $this->actingAs($owner);
        $workspace = app(Workspace::class);
        $workspace->bindTo('conv-google-import');
        $workspace->ownedBy($owner);

        $message = (string) app(OpenCampaign::class)->handle(new ToolRequest([
            'platform' => 'google',
            'campaign_id' => '222',
        ]));

        $this->assertStringContainsString('Opened "Imported Search"', $message);
        $this->assertSame('google', Campaign::query()->firstOrFail()->platform);
    }
}
