<?php

namespace Tests\Feature;

use App\Agent\Choices;
use App\Agent\Workspace;
use App\Mcp\Tools\GoogleAds\OfferCampaignOptions;
use App\Models\ConversationState;
use App\Models\GoogleAdsCampaign;
use App\Models\GoogleAdsConnection;
use App\Models\User;
use App\Services\GoogleAdsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Laravel\Mcp\Request;
use Tests\TestCase;

class GoogleAdsControllerTest extends TestCase
{
    use RefreshDatabase;

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

        // Google calls go through the same guardrails as Meta's now, so the
        // accounts these tests reach have to be on the allow list like any
        // other. Written without hyphens, which is how the check normalises a
        // Google customer id and how an admin would enter one.
        config([
            'platforms.guardrails.allowed_ad_accounts' => ['1234567890', '9876543210'],
        ]);
    }

    public function test_google_chat_uses_the_shared_chat_interface(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)->get(route('googlechat.show'));

        $response->assertOk()
            ->assertSee('Google Ads chat')
            ->assertSee('Ask Google Ads to list, create, or update campaigns...');
    }

    public function test_unauthenticated_users_are_redirected_from_google_ads_pages(): void
    {
        $this->get(route('google-ads.setup'))->assertRedirect(route('login'));
        $this->get(route('google-ads.create'))->assertRedirect(route('login'));
    }

    public function test_user_can_select_an_accessible_google_ads_account(): void
    {
        $user = User::factory()->create();
        $connection = GoogleAdsConnection::factory()->create([
            'user_id' => $user->id,
            'customer_id' => null,
            'available_customer_ids' => ['123-456-7890', '987-654-3210'],
        ]);
        $response = $this->actingAs($user)->put(route('google-ads.setup.save'), [
            'customer_id' => '123-456-7890',
            'manager_customer_id' => '987-654-3210',
        ]);

        $response->assertRedirect(route('google-ads.setup'));
        $this->assertDatabaseHas('google_ads_connections', ['user_id' => $user->id, 'customer_id' => '123-456-7890']);
        $this->assertDatabaseHas('google_ads_connections', ['user_id' => $user->id, 'manager_customer_id' => '987-654-3210']);
        $this->assertSame($connection->refresh_token, GoogleAdsConnection::firstOrFail()->refresh_token);
    }

    public function test_campaign_location_scope_offers_the_requested_radio_options(): void
    {
        $response = app(OfferCampaignOptions::class)->handle(new Request([
            'field' => 'location_scope',
        ]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertSame([
            'United States',
            'Canada',
            'United Kingdom',
            'Other',
        ], array_map(fn (array $option): string => $option['label'], $payload['offered'] ?? []));
    }

    public function test_google_ads_choice_questions_are_specific_and_do_not_offer_other(): void
    {
        app(OfferCampaignOptions::class)->handle(new Request([
            'field' => 'campaign_type',
        ]));
        app(OfferCampaignOptions::class)->handle(new Request([
            'field' => 'networks',
        ]));
        app(OfferCampaignOptions::class)->handle(new Request([
            'field' => 'positive_geo_target_type',
        ]));
        app(OfferCampaignOptions::class)->handle(new Request([
            'field' => 'negative_geo_target_type',
        ]));

        $all = app(Choices::class)->all();
        $byField = collect($all)->keyBy('field');

        $this->assertSame('Which campaign type should we create?', $byField['campaign_type']['question'] ?? null);
        $this->assertSame('Which Google networks should we target?', $byField['networks']['question'] ?? null);
        $this->assertSame('How should included locations work?', $byField['positive_geo_target_type']['question'] ?? null);
        $this->assertSame('How should excluded locations work?', $byField['negative_geo_target_type']['question'] ?? null);

        foreach (['campaign_type', 'networks'] as $field) {
            $labels = array_map(fn (array $option): string => $option['label'], $byField[$field]['options'] ?? []);
            $this->assertNotContains('Other', $labels);
        }
    }

    public function test_daily_budget_offers_other_with_the_correct_question(): void
    {
        app(OfferCampaignOptions::class)->handle(new Request([
            'field' => 'daily_budget',
            'suggestion' => '50',
            'alternative' => '100',
        ]));

        $choice = collect(app(Choices::class)->all())->firstWhere('field', 'daily_budget');

        $this->assertSame('What daily budget should we use?', $choice['question'] ?? null);
        $this->assertSame(['50', '100', 'Other'], array_map(
            fn (array $option): string => $option['label'],
            $choice['options'] ?? [],
        ));
    }

    public function test_google_ads_account_context_keeps_the_full_list_and_selected_name(): void
    {
        $workspace = app(Workspace::class);
        $workspace->bindTo('google-chat-account-context-test');
        $accounts = [
            ['account_name' => 'Northwind', 'customer_id' => '1234567890'],
            ['account_name' => 'Contoso', 'customer_id' => '9876543210'],
        ];

        $workspace->rememberGoogleAdsAccounts($accounts);
        $workspace->rememberGoogleAdsAccount($accounts[1]);

        $state = ConversationState::query()->where('conversation_id', 'google-chat-account-context-test')->firstOrFail();

        $this->assertSame($accounts, $state->google_ads_accounts);
        $this->assertSame('9876543210', $state->google_ads_account_id);
        $this->assertSame('Contoso', $state->google_ads_account_name);
    }

    public function test_user_must_connect_google_ads_before_creating_a_live_campaign(): void
    {
        $user = User::factory()->create();
        $response = $this->actingAs($user)->post(route('google-ads.store'), ['customer_id' => '123-456-7890', 'campaign_name' => 'Autumn product launch', 'ad_group_name' => 'Marketing teams', 'headline_1' => 'Launch campaigns faster', 'headline_2' => 'Keep every approval moving', 'headline_3' => 'Grow with confidence', 'description_1' => 'Plan, review, and publish marketing work from one focused workspace.', 'description_2' => 'Create clear campaigns and keep approvals moving.', 'final_url' => 'https://example.com/marketing', 'keywords' => 'marketing automation, campaign workflow', 'daily_budget' => '25.00']);

        $response->assertRedirect(route('google-ads.create'))->assertSessionHasErrors('connection');
        $this->assertDatabaseCount('google_ads_campaigns', 0);
        $this->assertDatabaseCount('google_ads_drafts', 0);
    }

    public function test_user_can_create_a_paused_live_google_ads_campaign(): void
    {
        $user = User::factory()->create();
        GoogleAdsConnection::factory()->for($user)->create([
            'customer_id' => '123-456-7890',
            'available_customer_ids' => ['123-456-7890', '987-654-3210'],
            'manager_customer_id' => '987-654-3210',
            'refresh_token' => 'refresh-token',
        ]);
        Http::fake([
            'oauth2.googleapis.com/token' => Http::response(['access_token' => 'access-token']),
            'googleads.googleapis.com/v25/customers/1234567890/campaignBudgets:mutate' => Http::response([
                'results' => [
                    ['resourceName' => 'customers/1234567890/campaignBudgets/111'],
                ],
            ]),
            'googleads.googleapis.com/v25/customers/1234567890/campaigns:mutate' => function ($request) {
                $payload = $request->data();
                $this->assertTrue(array_key_exists('maximizeConversions', data_get($payload, 'operations.0.create')));
                $this->assertSame('SEARCH', data_get($payload, 'operations.0.create.advertisingChannelType'));
                $this->assertSame('DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING', data_get($payload, 'operations.0.create.containsEuPoliticalAdvertising'));

                return Http::response([
                    'results' => [
                        ['resourceName' => 'customers/1234567890/campaigns/222'],
                    ],
                ]);
            },
            'googleads.googleapis.com/v25/customers/1234567890/adGroups:mutate' => Http::response([
                'results' => [
                    ['resourceName' => 'customers/1234567890/adGroups/333'],
                ],
            ]),
            'googleads.googleapis.com/v25/customers/1234567890/adGroupCriteria:mutate' => Http::response([
                'results' => [
                    ['resourceName' => 'customers/1234567890/adGroupCriteria/333~444'],
                ],
            ]),
            'googleads.googleapis.com/v25/customers/1234567890/adGroupAds:mutate' => Http::response([
                'results' => [
                    ['resourceName' => 'customers/1234567890/adGroupAds/333~555'],
                ],
            ]),
        ]);

        $response = $this->actingAs($user)->post(route('google-ads.store'), [
            'customer_id' => '123-456-7890',
            'campaign_name' => 'Autumn product launch',
            'ad_group_name' => 'Marketing teams',
            'headline_1' => 'Launch campaigns faster',
            'headline_2' => 'Keep every approval moving',
            'headline_3' => 'Grow with confidence',
            'description_1' => 'Plan, review, and publish marketing work from one focused workspace.',
            'description_2' => 'Create clear campaigns and keep approvals moving.',
            'final_url' => 'https://example.com/marketing',
            'keywords' => 'marketing automation, campaign workflow',
            'daily_budget' => '25.00',
        ]);

        $response->assertRedirect(route('google-ads.campaigns'));
        $this->assertDatabaseHas('google_ads_campaigns', [
            'user_id' => $user->id,
            'google_campaign_id' => '222',
            'google_campaign_budget_id' => '111',
            'google_ad_group_id' => '333',
            'google_ad_id' => '555',
            'status' => 'paused',
        ]);
    }

    public function test_display_campaign_uses_display_resources_and_image_assets(): void
    {
        $user = User::factory()->create();
        $connection = GoogleAdsConnection::factory()->for($user)->create([
            'customer_id' => '123-456-7890',
            'refresh_token' => 'refresh-token',
        ]);
        Http::fake([
            'oauth2.googleapis.com/token' => Http::response(['access_token' => 'access-token']),
            'googleads.googleapis.com/v25/customers/1234567890/campaignBudgets:mutate' => Http::response([
                'results' => [['resourceName' => 'customers/1234567890/campaignBudgets/111']],
            ]),
            'googleads.googleapis.com/v25/customers/1234567890/campaigns:mutate' => function ($request) {
                $this->assertSame('DISPLAY', data_get($request->data(), 'operations.0.create.advertisingChannelType'));
                $this->assertTrue(data_get($request->data(), 'operations.0.create.networkSettings.targetContentNetwork'));

                return Http::response(['results' => [['resourceName' => 'customers/1234567890/campaigns/222']]]);
            },
            'googleads.googleapis.com/v25/customers/1234567890/adGroups:mutate' => Http::response([
                'results' => [['resourceName' => 'customers/1234567890/adGroups/333']],
            ]),
            'googleads.googleapis.com/v25/customers/1234567890/adGroupAds:mutate' => function ($request) {
                $ad = data_get($request->data(), 'operations.0.create.ad.responsiveDisplayAd');
                $this->assertSame('Acme Display', $ad['businessName']);
                $this->assertSame('customers/1234567890/assets/10', $ad['marketingImages'][0]['asset']);
                $this->assertSame('customers/1234567890/assets/11', $ad['squareMarketingImages'][0]['asset']);

                return Http::response(['results' => [['resourceName' => 'customers/1234567890/adGroupAds/333~555']]]);
            },
        ]);

        $ids = app(GoogleAdsService::class)->createPausedCampaign($connection, [
            'name' => 'Display launch',
            'campaign_type' => 'Display',
            'bidding_strategy' => 'max_conversions',
            'daily_budget' => 30,
            'ad_group_name' => 'Display audience',
            'business_name' => 'Acme Display',
            'long_headline' => 'A long display headline',
            'headlines' => ['Display one', 'Display two', 'Display three'],
            'descriptions' => ['Display description one', 'Display description two'],
            'marketing_image_assets' => ['customers/1234567890/assets/10'],
            'square_marketing_image_assets' => ['customers/1234567890/assets/11'],
            'logo_image_assets' => [],
            'final_url' => 'https://example.com/display',
        ]);

        $this->assertSame(['budget_id' => '111', 'campaign_id' => '222', 'ad_group_id' => '333', 'ad_id' => '555'], $ids);
    }

    public function test_performance_max_campaign_creates_asset_group_links(): void
    {
        $user = User::factory()->create();
        $connection = GoogleAdsConnection::factory()->for($user)->create([
            'customer_id' => '123-456-7890',
            'refresh_token' => 'refresh-token',
        ]);
        Http::fake([
            'oauth2.googleapis.com/token' => Http::response(['access_token' => 'access-token']),
            'googleads.googleapis.com/v25/customers/1234567890/campaignBudgets:mutate' => Http::response([
                'results' => [['resourceName' => 'customers/1234567890/campaignBudgets/111']],
            ]),
            'googleads.googleapis.com/v25/customers/1234567890/campaigns:mutate' => Http::response([
                'results' => [['resourceName' => 'customers/1234567890/campaigns/222']],
            ]),
            'googleads.googleapis.com/v25/customers/1234567890/assetGroups:mutate' => Http::response([
                'results' => [['resourceName' => 'customers/1234567890/assetGroups/333']],
            ]),
            'googleads.googleapis.com/v25/customers/1234567890/assetGroupAssets:mutate' => function ($request) {
                $operations = $request->data()['operations'];
                $this->assertSame('HEADLINE', $operations[0]['create']['fieldType']);
                $this->assertSame('MARKETING_IMAGE', $operations[6]['create']['fieldType']);

                return Http::response(['results' => []]);
            },
        ]);

        $ids = app(GoogleAdsService::class)->createPausedCampaign($connection, [
            'name' => 'PMax launch',
            'campaign_type' => 'performance_max',
            'bidding_strategy' => 'max_conversions',
            'daily_budget' => 40,
            'asset_group_name' => 'PMax assets',
            'final_url' => 'https://example.com/pmax',
            'headlines' => [
                'customers/1234567890/assets/10',
                'customers/1234567890/assets/11',
                'customers/1234567890/assets/12',
            ],
            'long_headlines' => ['customers/1234567890/assets/13'],
            'descriptions' => [
                'customers/1234567890/assets/14',
                'customers/1234567890/assets/15',
            ],
            'marketing_image_assets' => ['customers/1234567890/assets/16'],
            'square_marketing_image_assets' => ['customers/1234567890/assets/17'],
            'logo_image_assets' => [],
            'youtube_video_assets' => [],
        ]);

        $this->assertSame(['budget_id' => '111', 'campaign_id' => '222', 'asset_group_id' => '333', 'ad_group_id' => null, 'ad_id' => null], $ids);
    }

    public function test_google_ads_video_asset_is_created_from_a_youtube_video_id(): void
    {
        $user = User::factory()->create();
        $connection = GoogleAdsConnection::factory()->for($user)->create([
            'customer_id' => '123-456-7890',
            'refresh_token' => 'refresh-token',
        ]);
        Http::fake([
            'oauth2.googleapis.com/token' => Http::response(['access_token' => 'access-token']),
            'googleads.googleapis.com/v25/customers/1234567890/assets:mutate' => function ($request) {
                $asset = data_get($request->data(), 'operations.0.create');
                $this->assertSame('YOUTUBE_VIDEO', $asset['type']);
                $this->assertSame('youtube-video-asset', $asset['name']);
                $this->assertSame('dQw4w9WgXcQ', $asset['youtubeVideoAsset']['youtubeVideoId']);

                return Http::response(['results' => [['resourceName' => 'customers/1234567890/assets/444']]]);
            },
        ]);

        $resource = app(GoogleAdsService::class)
            ->createYouTubeVideoAsset($connection, 'dQw4w9WgXcQ', 'youtube-video-asset');

        $this->assertSame('customers/1234567890/assets/444', $resource);
    }

    public function test_user_can_fetch_enabled_child_google_ads_accounts(): void
    {
        $user = User::factory()->create();
        $connection = GoogleAdsConnection::factory()->for($user)->create([
            'customer_id' => null,
            'manager_customer_id' => '987-654-3210',
            'available_customer_ids' => ['987-654-3210'],
        ]);
        Http::fake([
            'oauth2.googleapis.com/token' => Http::response(['access_token' => 'access-token']),
            'googleads.googleapis.com/v25/customers/9876543210/googleAds:searchStream' => Http::response([
                ['results' => [
                    ['customerClient' => ['clientCustomer' => 'customers/1234567890', 'manager' => false, 'status' => 'ENABLED']],
                    ['customerClient' => ['clientCustomer' => 'customers/2222222222', 'manager' => true, 'status' => 'ENABLED']],
                    ['customerClient' => ['clientCustomer' => 'customers/3333333333', 'manager' => false, 'status' => 'CANCELED']],
                ]],
            ]),
        ]);

        $response = $this->actingAs($user)->post(route('google-ads.setup.child-accounts'));

        $response->assertRedirect(route('google-ads.setup'));
        $this->assertSame(['987-654-3210', '1234567890'], $connection->refresh()->available_customer_ids);
    }

    public function test_existing_google_ads_connection_skips_oauth_when_customer_id_and_refresh_token_are_present(): void
    {
        $user = User::factory()->create();
        config([
            'services.google_ads.developer_token' => 'developer-token',
            'services.google_ads.oauth_client_id' => 'oauth-client-id',
            'services.google_ads.oauth_client_secret' => 'oauth-client-secret',
        ]);
        GoogleAdsConnection::factory()->for($user)->create([
            'customer_id' => '123-456-7890',
            'refresh_token' => 'already-valid-refresh-token',
        ]);

        $response = $this->actingAs($user)->get(route('google-ads.oauth.authorize'));

        $response->assertRedirect(route('google-ads.setup'));
        $response->assertSessionHas('status', 'Google Ads is already connected for this account.');
    }

    public function test_user_can_authorize_google_ads_and_store_the_refresh_token(): void
    {
        $user = User::factory()->create();
        config([
            'services.google_ads.developer_token' => 'developer-token',
            'services.google_ads.oauth_client_id' => 'oauth-client-id',
            'services.google_ads.oauth_client_secret' => 'oauth-client-secret',
        ]);

        $authorization = $this->actingAs($user)->get(route('google-ads.oauth.authorize'));
        parse_str(parse_url($authorization->headers->get('Location'), PHP_URL_QUERY), $query);
        Http::preventStrayRequests();
        Http::fake([
            'oauth2.googleapis.com/token' => Http::response(['refresh_token' => 'new-refresh-token', 'access_token' => 'access-token']),
            'openidconnect.googleapis.com/v1/userinfo' => Http::response(['email' => 'ads@example.com']),
            'googleads.googleapis.com/v25/customers:listAccessibleCustomers' => Http::response(['resourceNames' => ['customers/1234567890']]),
        ]);

        $response = $this->get(route('google-ads.oauth.callback', ['code' => 'authorization-code', 'state' => $query['state']]));

        $response->assertRedirect(route('google-ads.setup'));
        $this->assertSame('new-refresh-token', GoogleAdsConnection::firstOrFail()->refresh_token);
        $this->assertSame('ads@example.com', GoogleAdsConnection::firstOrFail()->google_email);
        $this->assertSame('1234567890', GoogleAdsConnection::firstOrFail()->customer_id);
        Http::assertSent(function ($request): bool {
            return $request->url() === 'https://oauth2.googleapis.com/token'
                && $request['code'] === 'authorization-code';
        });
    }

    public function test_google_ads_callback_rejects_an_invalid_state_without_exchanging_a_code(): void
    {
        $user = User::factory()->create();
        GoogleAdsConnection::factory()->for($user)->create();
        Http::preventStrayRequests();

        $response = $this->actingAs($user)->withSession(['google_ads.oauth_state' => 'expected-state'])
            ->get(route('google-ads.oauth.callback', ['code' => 'authorization-code', 'state' => 'wrong-state']));

        $response->assertRedirect(route('google-ads.setup'));
        $response->assertSessionHasErrors('connection');
        Http::assertNothingSent();
    }

    public function test_ad_draft_requires_google_ads_input(): void
    {
        $user = User::factory()->create();
        $response = $this->actingAs($user)->from(route('google-ads.create'))->post(route('google-ads.store'), []);

        $response->assertRedirect(route('google-ads.create'));
        $response->assertSessionHasErrors(['campaign_name', 'ad_group_name', 'headline_1', 'headline_2', 'description_1', 'final_url', 'keywords', 'daily_budget']);
        $this->assertDatabaseCount('google_ads_drafts', 0);
    }

    public function test_user_can_create_update_and_delete_a_google_ads_campaign_test_record(): void
    {
        $user = User::factory()->create();
        GoogleAdsConnection::factory()->for($user)->create();

        $create = $this->actingAs($user)->post(route('google-ads.campaigns.store'), [
            'name' => 'Spring launch',
            'status' => 'draft',
            'daily_budget' => '40.00',
            'ad_group_name' => 'Primary audience',
            'final_url' => 'https://example.com/spring',
        ]);

        $create->assertRedirect(route('google-ads.campaigns'));
        $campaign = GoogleAdsCampaign::firstOrFail();
        $this->assertSame($user->id, $campaign->user_id);

        $this->actingAs($user)->put(route('google-ads.campaigns.update', $campaign), [
            'name' => 'Spring launch updated',
            'status' => 'enabled',
            'daily_budget' => '50.00',
        ])->assertRedirect(route('google-ads.campaigns'));

        $this->assertDatabaseHas('google_ads_campaigns', [
            'id' => $campaign->id,
            'name' => 'Spring launch updated',
            'status' => 'enabled',
        ]);

        $this->actingAs($user)->delete(route('google-ads.campaigns.destroy', $campaign))
            ->assertRedirect(route('google-ads.campaigns'));
        $this->assertDatabaseMissing('google_ads_campaigns', ['id' => $campaign->id]);
    }

    public function test_user_cannot_update_another_users_google_ads_campaign(): void
    {
        $owner = User::factory()->create();
        $otherUser = User::factory()->create();
        $campaign = GoogleAdsCampaign::create(['user_id' => $owner->id, 'name' => 'Private campaign', 'status' => 'draft']);

        $this->actingAs($otherUser)->put(route('google-ads.campaigns.update', $campaign), [
            'name' => 'Changed',
            'status' => 'paused',
        ])->assertNotFound();
    }
}
