<?php

namespace Tests\Feature;

use App\Agent\Choices;
use App\Agent\TikTokAdsAgent;
use App\Campaigns\TikTokCampaignFlow;
use App\Http\Controllers\TikTokChatController;
use App\Mcp\Servers\TikTokServer;
use App\Mcp\Tools\TikTok\CreateTikTokAd;
use App\Mcp\Tools\TikTok\CreateTikTokPixel;
use App\Mcp\Tools\TikTok\FinalizeTikTokCampaign;
use App\Mcp\Tools\TikTok\ListAdvertisers;
use App\Mcp\Tools\TikTok\ListIdentities;
use App\Mcp\Tools\TikTok\ListTikTokPosts;
use App\Mcp\Tools\TikTok\OfferCampaignOptions;
use App\Mcp\Tools\TikTok\RetryTikTokAdGroup;
use App\Mcp\Tools\TikTok\SaveTikTokDraft;
use App\Mcp\Tools\TikTok\SelectAdvertiser;
use App\Mcp\Tools\TikTok\SelectIdentity;
use App\Mcp\Tools\TikTok\SelectTikTokImageCreative;
use App\Mcp\Tools\TikTok\SelectTikTokVideoCreative;
use App\Mcp\Tools\TikTok\UpdateCampaignDraft;
use App\Mcp\Tools\TikTok\ValidateTikTokCampaignDraft;
use App\Models\Asset;
use App\Models\Campaign;
use App\Models\Creative;
use App\Models\TikTokConnection;
use App\Models\User;
use App\Services\TikTokService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Laravel\Mcp\Request;
use ReflectionMethod;
use Tests\TestCase;

class TikTokChatControllerTest extends TestCase
{
    use RefreshDatabase;

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

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

        $response->assertOk()
            ->assertSee('TikTok Ads chat')
            ->assertSee('Connect TikTok Ads')
            ->assertSee('Campaigns are prepared in paused state.')
            ->assertSee('TikTok setup')
            ->assertSee('Campaign')
            ->assertSee('Ad group')
            ->assertSee('Ad')
            ->assertSee('Next: Advertiser account / Verified advertiser account')
            ->assertDontSee('id="composer"')
            ->assertSee('sendUrl');
    }

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

        $this->actingAs($user)
            ->get(route('tiktok.setup'))
            ->assertOk()
            ->assertSee('Connect your TikTok Ads account')
            ->assertSee(route('tiktok.oauth.authorize'))
            ->assertDontSee('Connection status');

        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => 'advertiser-1',
            'advertiser_name' => 'Test advertiser',
        ]);

        $this->actingAs($user)
            ->get(route('tiktok.setup'))
            ->assertOk()
            ->assertSee('Connected')
            ->assertSee('advertiser-1')
            ->assertSee('Test advertiser');
    }

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

        $this->actingAs($user)
            ->postJson(route('tiktokchat.send'), ['message' => 'Show campaign objectives'])
            ->assertStatus(409)
            ->assertJsonPath('message', 'Connect TikTok Ads before continuing.');
    }

    public function test_disconnected_user_reopening_a_conversation_still_sees_connect_prompt(): void
    {
        $user = User::factory()->create();
        $conversationId = (string) Str::uuid7();
        DB::table('agent_conversations')->insert([
            'id' => $conversationId,
            'participant_type' => $user->getMorphClass(),
            'participant_id' => $user->getKey(),
            'chat_type' => 'tiktok_ads',
            'title' => 'TikTok campaign chat',
            'created_at' => now(),
            'updated_at' => now(),
        ]);
        DB::table('agent_conversation_messages')->insert([
            'id' => (string) Str::uuid7(),
            'conversation_id' => $conversationId,
            'participant_type' => $user->getMorphClass(),
            'participant_id' => $user->getKey(),
            'agent' => TikTokAdsAgent::class,
            'role' => 'user',
            'content' => 'Previous TikTok campaign request',
            'attachments' => '[]',
            'tool_calls' => '[]',
            'tool_results' => '[]',
            'usage' => '[]',
            'meta' => '[]',
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        $this->actingAs($user)
            ->get(route('tiktokchat.show', ['conversation' => $conversationId]))
            ->assertOk()
            ->assertSee('Connect TikTok Ads')
            ->assertSee('Previous TikTok campaign request')
            ->assertDontSee('id="composer"');
    }

    public function test_tiktok_mcp_server_exposes_advertiser_account_tools(): void
    {
        $tools = new \ReflectionProperty(TikTokServer::class, 'tools');

        $this->assertContains(ListAdvertisers::class, $tools->getDefaultValue());
        $this->assertContains(SelectAdvertiser::class, $tools->getDefaultValue());
        $this->assertContains(ListIdentities::class, $tools->getDefaultValue());
        $this->assertContains(ListTikTokPosts::class, $tools->getDefaultValue());
        $this->assertContains(SelectIdentity::class, $tools->getDefaultValue());
        $this->assertContains(CreateTikTokAd::class, $tools->getDefaultValue());
        $this->assertContains(CreateTikTokPixel::class, $tools->getDefaultValue());
        $this->assertContains(RetryTikTokAdGroup::class, $tools->getDefaultValue());
        $this->assertContains(SaveTikTokDraft::class, $tools->getDefaultValue());
        $this->assertContains(UpdateCampaignDraft::class, $tools->getDefaultValue());
        $this->assertContains(ValidateTikTokCampaignDraft::class, $tools->getDefaultValue());
        $this->assertContains(FinalizeTikTokCampaign::class, $tools->getDefaultValue());
    }

    public function test_tiktok_creative_pickers_provide_image_previews_and_video_details(): void
    {
        $user = User::factory()->create();
        $imageAsset = Asset::query()->create([
            'kind' => 'image',
            'source' => 'generated',
            'disk' => 'local',
            'path' => 'creatives/image.png',
            'original_name' => 'image.png',
            'mime' => 'image/png',
        ]);
        $videoAsset = Asset::query()->create([
            'kind' => 'video',
            'source' => 'generated',
            'disk' => 'local',
            'path' => 'creatives/video.mp4',
            'original_name' => 'video.mp4',
            'mime' => 'video/mp4',
        ]);
        Creative::query()->create([
            'user_id' => $user->id,
            'type' => 'image',
            'status' => 'completed',
            'source' => 'prompt',
            'title' => 'Image choice',
            'asset_id' => $imageAsset->id,
        ]);
        Creative::query()->create([
            'user_id' => $user->id,
            'type' => 'video',
            'status' => 'completed',
            'source' => 'prompt',
            'title' => 'Video choice',
            'duration' => 15,
            'asset_id' => $videoAsset->id,
        ]);
        Storage::disk('local')->put($imageAsset->path, 'image-bytes');
        Storage::disk('local')->put($videoAsset->path, 'video-bytes');
        $this->actingAs($user);

        app(SelectTikTokImageCreative::class)->handle(new Request([]));
        $imageChoices = app(Choices::class)->drain();
        app(SelectTikTokVideoCreative::class)->handle(new Request([]));
        $videoChoices = app(Choices::class)->drain();

        $this->assertStringContainsString('/assets/'.$imageAsset->id.'/preview', $imageChoices[0]['options'][0]['image']);
        $this->assertSame('15 seconds - video.mp4', $videoChoices[0]['options'][0]['hint']);
    }

    public function test_user_can_create_a_disabled_tiktok_ad_and_receive_platform_ids(): void
    {
        $user = User::factory()->create();
        config([
            'services.tiktok.account_currency' => 'AED',
            'services.tiktok.usd_to_account_rate' => 3.6725,
        ]);
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
            'identity_id' => 'identity-123',
            'identity_type' => 'BC_AUTH_TT',
            'identity_authorized_bc_id' => 'bc-123',
            'tiktok_item_id' => 'post-123',
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/campaign/create/' => Http::response(['code' => 0, 'data' => ['campaign_id' => 'campaign-123']]),
            'business-api.tiktok.com/open_api/v1.3/adgroup/create/' => Http::response(['code' => 0, 'data' => ['adgroup_id' => 'adgroup-456']]),
            'business-api.tiktok.com/open_api/v1.3/ad/create/' => Http::response(['code' => 0, 'data' => ['ad_ids' => ['ad-789']]]),
        ]);
        $this->actingAs($user);

        $response = app(CreateTikTokAd::class)->handle(new Request([
            'campaign_name' => 'Autumn traffic campaign',
            'objective_type' => 'TRAFFIC',
            'adgroup_name' => 'US website traffic',
            'promotion_type' => 'WEBSITE',
            'optimization_goal' => 'LANDING_PAGE_VIEW',
            'billing_event' => 'CPC',
            'budget_mode' => 'BUDGET_MODE_DAY',
            'budget' => 20,
            'pacing' => 'PACING_MODE_SMOOTH',
            'schedule_type' => 'SCHEDULE_FROM_NOW',
            'schedule_start_time' => '2026-09-28 09:00:00',
            'location_ids' => ['6252001'],
            'ad_name' => 'Autumn traffic ad',
            'ad_format' => 'SINGLE_VIDEO',
            'ad_text' => 'Explore our autumn collection.',
            'call_to_action' => 'LEARN_MORE',
            'landing_page_url' => 'https://example.com/autumn',
            'pixel_id' => 'pixel-123',
            'conversion_event' => 'SubmitForm',
            'video_id' => 'video-123',
        ]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertSame('campaign-123', $payload['campaign_id']);
        $this->assertSame('adgroup-456', $payload['adgroup_id']);
        $this->assertSame('ad-789', $payload['ad_id']);
        $this->assertSame('DISABLE', $payload['operation_status']);
        $this->assertSame(73.45, $payload['budget']);
        $this->assertSame('AED', $payload['currency']);
        $this->assertDatabaseHas('campaigns', [
            'user_id' => $user->id,
            'platform' => 'tiktok',
            'status' => 'paused',
            'external_campaign_id' => 'campaign-123',
            'budget' => 73.45,
            'currency' => 'AED',
        ]);
        Http::assertSentCount(3);
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/campaign/create/')
            && data_get($request->data(), 'operation_status') === 'DISABLE'
            && data_get($request->data(), 'budget_mode') === 'BUDGET_MODE_INFINITE'
            && data_get($request->data(), 'budget_optimize_on') === false);
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/adgroup/create/')
            && data_get($request->data(), 'operation_status') === 'DISABLE'
            && data_get($request->data(), 'budget_mode') === 'BUDGET_MODE_DAY'
            && data_get($request->data(), 'budget') === 73.45
            && data_get($request->data(), 'optimization_goal') === 'CLICK'
            && data_get($request->data(), 'pixel_id') === 'pixel-123'
            && data_get($request->data(), 'conversion_event') === 'SubmitForm'
            && data_get($request->data(), 'billing_event') === 'CPC'
            && data_get($request->data(), 'bid_type') === 'BID_TYPE_NO_BID'
            && data_get($request->data(), 'placement_type') === 'PLACEMENT_TYPE_NORMAL'
            && data_get($request->data(), 'placements') === ['PLACEMENT_TIKTOK']);
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/ad/create/')
            && data_get($request->data(), 'creatives.0.operation_status') === 'DISABLE'
            && data_get($request->data(), 'creatives.0.identity_type') === 'BC_AUTH_TT'
            && data_get($request->data(), 'creatives.0.identity_authorized_bc_id') === 'bc-123'
            && data_get($request->data(), 'creatives.0.tiktok_item_id') === 'post-123'
            && data_get($request->data(), 'creatives.0.tracking_pixel_id') === 'pixel-123'
            && data_get($request->data(), 'creatives.0.tracking_event') === 'SubmitForm'
            && data_get($request->data(), 'creatives.0.video_id') === null);
    }

    public function test_minimal_tiktok_brief_derives_safe_website_traffic_defaults(): void
    {
        $user = User::factory()->create();
        config([
            'services.tiktok.account_currency' => 'AED',
            'services.tiktok.usd_to_account_rate' => 3.6725,
            'services.tiktok.account_timezone' => 'Asia/Dubai',
            'services.tiktok.default_location_id' => '6252001',
        ]);
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
            'identity_id' => 'identity-123',
            'identity_type' => 'BC_AUTH_TT',
            'identity_authorized_bc_id' => 'bc-123',
            'tiktok_item_id' => 'post-123',
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/campaign/create/' => Http::response(['code' => 0, 'data' => ['campaign_id' => 'campaign-123']]),
            'business-api.tiktok.com/open_api/v1.3/adgroup/create/' => Http::response(['code' => 0, 'data' => ['adgroup_id' => 'adgroup-456']]),
            'business-api.tiktok.com/open_api/v1.3/ad/create/' => Http::response(['code' => 0, 'data' => ['ad_id' => 'ad-789']]),
        ]);
        $this->actingAs($user);

        $response = app(CreateTikTokAd::class)->handle(new Request([
            'campaign_name' => 'Minimal website traffic campaign',
            'budget' => 20,
            'landing_page_url' => 'https://example.com',
            'ad_text' => 'Explore our collection.',
        ]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertTrue($payload['ok']);
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/campaign/create/')
            && data_get($request->data(), 'objective_type') === 'TRAFFIC');
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/adgroup/create/')
            && data_get($request->data(), 'optimization_goal') === 'CLICK'
            && data_get($request->data(), 'billing_event') === 'CPC'
            && data_get($request->data(), 'bid_type') === 'BID_TYPE_NO_BID'
            && data_get($request->data(), 'budget') === 73.45
            && data_get($request->data(), 'location_ids') === ['6252001']
            && data_get($request->data(), 'placements') === ['PLACEMENT_TIKTOK']);
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/ad/create/')
            && data_get($request->data(), 'creatives.0.ad_format') === 'SINGLE_VIDEO'
            && data_get($request->data(), 'creatives.0.call_to_action') === 'LEARN_MORE');
    }

    public function test_tiktok_creation_reports_a_created_campaign_id_when_ad_group_creation_fails(): void
    {
        $connection = TikTokConnection::factory()->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/campaign/create/' => Http::response(['code' => 0, 'data' => ['campaign_id' => 'campaign-123']]),
            'business-api.tiktok.com/open_api/v1.3/adgroup/create/' => Http::response(['code' => 40002, 'message' => 'Invalid ad group settings.', 'data' => []]),
        ]);
        $created = [];

        try {
            app(TikTokService::class)->createPausedAd(
                $connection,
                ['campaign_name' => 'Campaign', 'objective_type' => 'TRAFFIC'],
                ['adgroup_name' => 'Ad group'],
                ['ad_name' => 'Ad'],
                created: $created,
            );
            $this->fail('Expected TikTok ad group creation to fail.');
        } catch (\RuntimeException $exception) {
            $this->assertStringContainsString('disabled campaign campaign-123', $exception->getMessage());
            $this->assertStringContainsString('Invalid ad group settings', $exception->getMessage());
        }

        $this->assertSame('campaign-123', $created['campaign_id']);
        Http::assertSentCount(2);
    }

    public function test_tiktok_creation_can_resume_an_existing_disabled_campaign_without_creating_a_duplicate(): void
    {
        $connection = TikTokConnection::factory()->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/adgroup/create/' => Http::response(['code' => 0, 'data' => ['adgroup_id' => 'adgroup-456']]),
            'business-api.tiktok.com/open_api/v1.3/ad/create/' => Http::response(['code' => 0, 'data' => ['ad_id' => 'ad-789']]),
        ]);

        $ids = app(TikTokService::class)->createPausedAd(
            $connection,
            [],
            ['adgroup_name' => 'Ad group', 'budget' => 20, 'budget_mode' => 'BUDGET_MODE_DAY'],
            ['ad_name' => 'Ad'],
            '1877556978266321',
        );

        $this->assertSame('1877556978266321', $ids['campaign_id']);
        Http::assertSentCount(2);
        Http::assertNotSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/campaign/create/'));
    }

    public function test_tiktok_search_placement_is_rejected_before_any_remote_mutation(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
            'identity_id' => 'identity-123',
            'identity_type' => 'CUSTOMIZED_USER',
        ]);
        Http::preventStrayRequests();
        $this->actingAs($user);

        $response = app(CreateTikTokAd::class)->handle(new Request([
            'campaign_name' => 'Search traffic campaign',
            'existing_campaign_id' => '1877556978266321',
            'objective_type' => 'TRAFFIC',
            'adgroup_name' => 'Search traffic ad group',
            'promotion_type' => 'WEBSITE',
            'optimization_goal' => 'LANDING_PAGE_VIEW',
            'billing_event' => 'CPC',
            'budget_mode' => 'BUDGET_MODE_DAY',
            'budget' => 20,
            'pacing' => 'PACING_MODE_SMOOTH',
            'schedule_type' => 'SCHEDULE_FROM_NOW',
            'schedule_start_time' => '2026-09-28 09:00:00',
            'location_ids' => ['6252001'],
            'placements' => ['PLACEMENT_TIKTOK_SEARCH'],
            'ad_name' => 'Search traffic ad',
            'ad_format' => 'SINGLE_VIDEO',
            'ad_text' => 'Explore our collection.',
            'call_to_action' => 'LEARN_MORE',
            'landing_page_url' => 'https://example.com',
            'video_id' => 'video-123',
        ]));

        $this->assertStringContainsString('Select TikTok to continue', (string) $response->content());
        Http::assertNothingSent();
    }

    public function test_reach_retry_normalizes_landing_page_view_to_reach_without_creating_duplicate_campaign(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
            'identity_id' => 'identity-123',
            'identity_type' => 'BC_AUTH_TT',
            'identity_authorized_bc_id' => 'bc-123',
            'tiktok_item_id' => 'post-123',
        ]);
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/adgroup/create/' => Http::response(['code' => 0, 'data' => ['adgroup_id' => 'adgroup-retry']]),
            'business-api.tiktok.com/open_api/v1.3/ad/create/' => Http::response(['code' => 0, 'data' => ['ad_id' => 'ad-retry']]),
        ]);
        $this->actingAs($user);

        $response = app(CreateTikTokAd::class)->handle(new Request([
            'campaign_name' => 'Reach campaign',
            'objective_type' => 'AWARENESS',
            'existing_campaign_id' => 'campaign-existing',
            'promotion_type' => 'WEBSITE',
            'optimization_goal' => 'LANDING_PAGE_VIEW',
            'budget' => 20,
            'landing_page_url' => 'https://example.com',
            'ad_text' => 'Reach more people.',
        ]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertSame('adgroup-retry', $payload['adgroup_id']);
        $this->assertSame('ad-retry', $payload['ad_id']);
        Http::assertNotSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/campaign/create/'));
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/adgroup/create/')
            && data_get($request->data(), 'optimization_goal') === 'REACH'
            && data_get($request->data(), 'billing_event') === 'CPM'
            && data_get($request->data(), 'bid_type') === 'BID_TYPE_CUSTOM'
            && data_get($request->data(), 'bid_price') > 0
            && data_get($request->data(), 'frequency_control_count') === 3
            && data_get($request->data(), 'frequency_control_period') === 'FREQUENCY_CONTROL_PERIOD_DAY');
    }

    public function test_reach_retry_uses_objective_and_frequency_cap_from_the_draft(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
            'identity_id' => 'identity-123',
            'identity_type' => 'BC_AUTH_TT',
            'identity_authorized_bc_id' => 'bc-123',
            'tiktok_item_id' => 'post-123',
        ]);
        $conversationId = (string) Str::uuid7();
        DB::table('agent_conversations')->insert([
            'id' => $conversationId,
            'participant_type' => $user->getMorphClass(),
            'participant_id' => $user->getKey(),
            'chat_type' => 'tiktok_ads',
            'title' => 'Reach retry',
            'tiktok_campaign_draft' => json_encode([
                'objective' => 'reach',
                'promotion_type' => 'website',
                'optimization_goal' => 'reach',
                'frequency_cap' => 2,
                'campaign_type' => 'manual',
                'creative_source' => 'tiktok_post',
                'creative_format' => 'single_video',
                'identity_mode' => 'spark',
                'validation_status' => 'passed',
            ]),
            'created_at' => now(),
            'updated_at' => now(),
        ]);
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/adgroup/create/' => Http::response(['code' => 0, 'data' => ['adgroup_id' => 'adgroup-retry']]),
            'business-api.tiktok.com/open_api/v1.3/ad/create/' => Http::response(['code' => 0, 'data' => ['ad_id' => 'ad-retry']]),
        ]);
        $this->actingAs($user);
        request()->merge(['conversation' => $conversationId]);

        app(CreateTikTokAd::class)->handle(new Request([
            'campaign_name' => 'Reach campaign',
            'existing_campaign_id' => '1877737603090946',
            'budget' => 20,
            'landing_page_url' => 'https://example.com',
            'ad_text' => 'Reach more people.',
        ]));

        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/adgroup/create/')
            && data_get($request->data(), 'optimization_goal') === 'REACH'
            && data_get($request->data(), 'frequency_control_count') === 2);
    }

    public function test_lead_generation_retry_normalizes_landing_page_view_to_lead_generation_without_duplicate_campaign(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
            'identity_id' => 'identity-123',
            'identity_type' => 'BC_AUTH_TT',
            'identity_authorized_bc_id' => 'bc-123',
            'tiktok_item_id' => 'post-123',
        ]);
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/adgroup/create/' => Http::response(['code' => 0, 'data' => ['adgroup_id' => 'lead-adgroup']]),
            'business-api.tiktok.com/open_api/v1.3/ad/create/' => Http::response(['code' => 0, 'data' => ['ad_id' => 'lead-ad']]),
        ]);
        $this->actingAs($user);

        $response = app(CreateTikTokAd::class)->handle(new Request([
            'campaign_name' => 'Lead generation campaign',
            'objective_type' => 'LEAD_GENERATION',
            'existing_campaign_id' => 'campaign-existing',
            'promotion_type' => 'WEBSITE',
            'optimization_goal' => 'LANDING_PAGE_VIEW',
            'budget' => 20,
            'landing_page_url' => 'https://example.com/leads',
            'ad_text' => 'Request a demo.',
        ]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertSame('lead-adgroup', $payload['adgroup_id']);
        $this->assertSame('lead-ad', $payload['ad_id']);
        Http::assertNotSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/campaign/create/'));
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/adgroup/create/')
            && data_get($request->data(), 'optimization_goal') === 'LEAD_GENERATION'
            && data_get($request->data(), 'promotion_type') === 'LEAD_GENERATION'
            && data_get($request->data(), 'billing_event') === 'OCPM'
            && data_get($request->data(), 'bid_type') === 'BID_TYPE_CUSTOM'
            && data_get($request->data(), 'bid_price') === 5.0
            && data_get($request->data(), 'conversion_bid_price') === 5.0);
    }

    public function test_single_tiktok_identity_is_automatically_selected_and_persisted(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/identity/get/*' => Http::response([
                'code' => 0,
                'data' => ['identity_list' => [[
                    'identity_id' => 'identity-123',
                    'identity_type' => 'BC_AUTH_TT',
                    'identity_authorized_bc_id' => '7258169903276949505',
                    'display_name' => 'Admedia TikTok',
                ]]],
            ]),
        ]);
        $this->actingAs($user);

        $response = app(ListIdentities::class)->handle(new Request([]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertTrue($payload['auto_selected']);
        $this->assertSame('identity-123', $payload['selected_identity']['identity_id']);
        $this->assertDatabaseHas('tik_tok_connections', [
            'user_id' => $user->id,
            'identity_id' => 'identity-123',
            'identity_authorized_bc_id' => '7258169903276949505',
            'identity_type' => 'BC_AUTH_TT',
            'identity_name' => 'Admedia TikTok',
        ]);
        $this->assertSame([], app(Choices::class)->all());
    }

    public function test_single_authorized_tiktok_post_is_automatically_selected(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
            'identity_id' => 'identity-123',
            'identity_type' => 'BC_AUTH_TT',
            'identity_authorized_bc_id' => 'bc-123',
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/identity/video/get/*' => Http::response([
                'code' => 0,
                'data' => ['video_list' => [[
                    'item_id' => 'post-123',
                    'text' => 'Authorized TikTok post',
                    'video_info' => ['duration' => 19.2, 'poster_url' => 'https://example.com/poster.jpg'],
                ]]],
            ]),
        ]);
        $this->actingAs($user);

        $response = app(ListTikTokPosts::class)->handle(new Request([]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertTrue($payload['auto_selected']);
        $this->assertSame('post-123', $payload['selected_post']['item_id']);
        $this->assertDatabaseHas('tik_tok_connections', ['user_id' => $user->id, 'tiktok_item_id' => 'post-123']);
        $this->assertSame([], app(Choices::class)->all());
    }

    public function test_multiple_tiktok_identities_are_offered_and_selected_identity_is_persisted(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/identity/get/*' => Http::response([
                'code' => 0,
                'data' => ['identity_list' => [
                    ['identity_id' => 'identity-123', 'identity_type' => 'BC_AUTH_TT', 'display_name' => 'Brand account'],
                    ['identity_id' => 'identity-456', 'identity_type' => 'CUSTOMIZED_USER', 'display_name' => 'Custom identity'],
                ]],
            ]),
        ]);
        $this->actingAs($user);

        $listResponse = app(ListIdentities::class)->handle(new Request([]));
        $listPayload = json_decode((string) $listResponse->content(), true);
        $offered = app(Choices::class)->drain();
        $selectResponse = app(SelectIdentity::class)->handle(new Request([
            'identity_id' => 'identity-456',
            'identity_type' => 'CUSTOMIZED_USER',
        ]));
        $selectPayload = json_decode((string) $selectResponse->content(), true);

        $this->assertTrue($listPayload['selection_required']);
        $this->assertSame(['BC_AUTH_TT|identity-123', 'CUSTOMIZED_USER|identity-456'], array_column($offered[0]['options'], 'value'));
        $this->assertSame('identity-456', $selectPayload['selected_identity']['identity_id']);
        $this->assertDatabaseHas('tik_tok_connections', [
            'user_id' => $user->id,
            'identity_id' => 'identity-456',
            'identity_type' => 'CUSTOMIZED_USER',
            'identity_name' => 'Custom identity',
        ]);
    }

    public function test_bc_identity_retry_reuses_existing_ad_group_and_sends_both_identity_ids(): void
    {
        $connection = TikTokConnection::factory()->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/ad/create/' => Http::response(['code' => 0, 'data' => ['ad_id' => 'ad-789']]),
        ]);

        $ids = app(TikTokService::class)->createPausedAd(
            $connection,
            [],
            [],
            [
                'ad_name' => 'Ad',
                'identity_type' => 'BC_AUTH_TT',
                'identity_id' => '672482c4-b8c3-583e-aad7-80625bd81c9d',
                'identity_authorized_bc_id' => '7258169903276949505',
            ],
            '1877558672213537',
            '1877559368812849',
        );

        $this->assertSame('1877558672213537', $ids['campaign_id']);
        $this->assertSame('1877559368812849', $ids['adgroup_id']);
        Http::assertSentCount(1);
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/ad/create/')
            && data_get($request->data(), 'creatives.0.identity_type') === 'BC_AUTH_TT'
            && data_get($request->data(), 'creatives.0.identity_id') === '672482c4-b8c3-583e-aad7-80625bd81c9d'
            && data_get($request->data(), 'creatives.0.identity_authorized_bc_id') === '7258169903276949505');
    }

    public function test_spark_ad_retry_uses_authorized_post_and_existing_parent_ids(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
            'identity_id' => 'bc-identity',
            'identity_type' => 'BC_AUTH_TT',
            'identity_authorized_bc_id' => '7258169903276949505',
            'tiktok_item_id' => '7689193553087515926',
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/ad/create/' => Http::response(['code' => 0, 'data' => ['ad_id' => 'ad-789']]),
        ]);
        $this->actingAs($user);

        $response = app(CreateTikTokAd::class)->handle(new Request([
            'campaign_name' => 'Resume Spark ad',
            'existing_campaign_id' => '1877563301436577',
            'existing_adgroup_id' => '1877563294261074',
            'budget' => 20,
            'landing_page_url' => 'https://example.com',
            'ad_text' => 'Explore our collection.',
        ]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertSame('ad-789', $payload['ad_id']);
        Http::assertSentCount(1);
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/ad/create/')
            && data_get($request->data(), 'creatives.0.identity_type') === 'BC_AUTH_TT'
            && data_get($request->data(), 'creatives.0.identity_id') === 'bc-identity'
            && data_get($request->data(), 'creatives.0.identity_authorized_bc_id') === '7258169903276949505'
            && data_get($request->data(), 'creatives.0.tiktok_item_id') === '7689193553087515926'
            && data_get($request->data(), 'creatives.0.image_ids') === null
            && data_get($request->data(), 'creatives.0.video_id') === null);
    }

    public function test_selecting_an_application_image_uploads_it_to_tiktok_and_caches_the_image_id(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
        ]);
        Storage::fake('local');
        Storage::disk('local')->put('creatives/image.png', 'image-contents');
        $asset = Asset::query()->create([
            'owner_type' => $user->getMorphClass(),
            'owner_id' => $user->getKey(),
            'kind' => 'image',
            'source' => 'generated',
            'disk' => 'local',
            'path' => 'creatives/image.png',
            'original_name' => 'image.png',
            'mime' => 'image/png',
        ]);
        $creative = Creative::query()->create([
            'user_id' => $user->id,
            'type' => 'image',
            'status' => 'completed',
            'source' => 'prompt',
            'title' => 'Selected image',
            'asset_id' => $asset->id,
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/file/image/ad/upload/' => Http::response([
                'code' => 0,
                'data' => ['image_id' => 'tiktok-image-123'],
            ]),
        ]);
        $this->actingAs($user);

        $first = json_decode((string) app(SelectTikTokImageCreative::class)->handle(new Request(['creative_id' => $creative->id]))->content(), true);
        $second = json_decode((string) app(SelectTikTokImageCreative::class)->handle(new Request(['creative_id' => $creative->id]))->content(), true);

        $this->assertSame('tiktok-image-123', $first['image_id']);
        $this->assertSame('tiktok-image-123', $second['image_id']);
        $this->assertDatabaseHas('asset_platform_refs', [
            'asset_id' => $asset->id,
            'platform' => 'tiktok',
            'ad_account_id' => '7452605154932572161',
            'external_hash' => 'tiktok-image-123',
        ]);
        Http::assertSentCount(1);
    }

    public function test_selecting_an_application_video_uploads_it_to_tiktok_and_returns_the_video_id(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
        ]);
        Storage::fake('local');
        Storage::disk('local')->put('creatives/video.mp4', 'video-contents');
        $asset = Asset::query()->create([
            'owner_type' => $user->getMorphClass(),
            'owner_id' => $user->getKey(),
            'kind' => 'video',
            'source' => 'generated',
            'disk' => 'local',
            'path' => 'creatives/video.mp4',
            'original_name' => 'video.mp4',
            'mime' => 'video/mp4',
        ]);
        $creative = Creative::query()->create([
            'user_id' => $user->id,
            'type' => 'video',
            'status' => 'completed',
            'source' => 'prompt',
            'title' => 'Selected video',
            'asset_id' => $asset->id,
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/file/video/ad/upload/' => Http::response([
                'code' => 0,
                'data' => ['video_id' => 'tiktok-video-123'],
            ]),
        ]);
        $this->actingAs($user);

        $response = app(SelectTikTokVideoCreative::class)->handle(new Request(['creative_id' => $creative->id]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertSame('tiktok-video-123', $payload['video_id']);
        $this->assertDatabaseHas('asset_platform_refs', [
            'asset_id' => $asset->id,
            'platform' => 'tiktok',
            'ad_account_id' => '7452605154932572161',
            'external_id' => 'tiktok-video-123',
        ]);
        Http::assertSentCount(1);
    }

    public function test_user_can_save_a_completed_tiktok_brief_as_a_local_paused_draft(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
            'advertiser_name' => 'Admedia TikTok',
        ]);
        $conversationId = (string) Str::uuid7();
        DB::table('agent_conversations')->insert([
            'id' => $conversationId,
            'participant_type' => $user->getMorphClass(),
            'participant_id' => $user->getKey(),
            'chat_type' => 'tiktok_ads',
            'title' => 'Validated campaign',
            'tiktok_campaign_draft' => json_encode([
                'objective' => 'traffic',
                'campaign_name' => 'Autumn traffic campaign',
                'daily_budget' => '50',
                'promotion_type' => 'website',
                'landing_page_url' => 'https://example.com/autumn',
                'campaign_type' => 'manual',
                'optimization_goal' => 'click',
                'placement' => 'tiktok',
                'targeting_mode' => 'automatic',
                'location_id' => 'US',
                'campaign_budget_optimization' => 'off',
                'schedule' => 'continuous',
                'bidding_strategy' => 'lowest_cost',
                'adgroup_optimization_goal' => 'click',
                'identity_mode' => 'spark',
                'identity_id' => 'identity-1',
                'creative_source' => 'tiktok_post',
                'creative_format' => 'single_video',
                'tiktok_item_id' => 'post-1',
                'ad_text' => 'Shop now',
                'call_to_action' => 'SHOP_NOW',
                'tracking_enabled' => 'no',
                'validation_status' => 'passed',
                'preview_confirmed' => 'yes',
                'creation_mode' => 'draft',
                'draft_confirmed' => 'yes',
            ]),
            'created_at' => now(),
            'updated_at' => now(),
        ]);
        $this->actingAs($user);

        $response = app(SaveTikTokDraft::class)->handle(new Request([
            'name' => 'Autumn traffic campaign',
            'objective' => 'traffic',
            'daily_budget' => 50,
            'destination' => 'website',
            'landing_url' => 'https://example.com/autumn',
            'country' => 'US',
            'campaign_type' => 'regular',
            'optimization_goal' => 'landing_page_view',
            'placement' => 'automatic',
            'targeting' => 'Adults interested in office furniture',
            'bidding_strategy' => 'lowest_cost',
            'schedule' => 'Start tomorrow and run continuously',
            'creative_summary' => 'One selected video',
            'conversation_id' => $conversationId,
        ]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertTrue($payload['ok']);
        $this->assertTrue($payload['saved_locally']);
        $this->assertFalse($payload['campaign']['created_on_tiktok']);
        $campaign = Campaign::query()->where('user_id', $user->id)->sole();
        $this->assertSame('tiktok', $campaign->platform);
        $this->assertSame('draft', $campaign->status);
        $this->assertSame('paused', $campaign->meta['delivery_mode']);
        $this->assertSame('7452605154932572161', $campaign->ad_account_id);
        $this->assertSame('manual', $campaign->meta['settings']['campaign_type']);
    }

    public function test_configured_tiktok_advertiser_is_selected_when_tiktok_authorizes_it(): void
    {
        $user = User::factory()->create();
        config([
            'services.tiktok.advertiser_id' => 'configured-advertiser',
            'services.tiktok.app_id' => 'test-app-id',
            'services.tiktok.app_secret' => 'test-app-secret',
        ]);
        TikTokConnection::factory()->for($user)->create(['access_token' => 'test-token']);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/oauth2/advertiser/get/*' => Http::response([
                'data' => [
                    'list' => [
                        ['advertiser_id' => 'configured-advertiser', 'advertiser_name' => 'Configured account'],
                        ['advertiser_id' => 'other-advertiser', 'advertiser_name' => 'Other account'],
                    ],
                ],
            ]),
        ]);

        $this->actingAs($user);
        $response = app(ListAdvertisers::class)->handle(new Request([]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertTrue($payload['ok']);
        $this->assertSame('configured-advertiser', $payload['selected_advertiser']['advertiser_id']);
        $this->assertDatabaseHas('tik_tok_connections', [
            'user_id' => $user->id,
            'advertiser_id' => 'configured-advertiser',
            'advertiser_name' => 'Configured account',
        ]);
        $choices = app(Choices::class)->all();
        $this->assertSame('tiktok_advertiser_id', $choices[0]['field']);
        $this->assertSame(['configured-advertiser', 'other-advertiser'], array_column($choices[0]['options'], 'value'));
        $this->assertStringContainsString('currently selected', $choices[0]['options'][0]['hint']);
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/oauth2/advertiser/get/?app_id=test-app-id&secret=test-app-secret&access_token=test-token&page=1&page_size=100'));
    }

    public function test_advertiser_lookup_reports_tiktok_provider_errors_instead_of_returning_an_empty_list(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create(['access_token' => 'test-token']);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/oauth2/advertiser/get/*' => Http::response([
                'code' => 40002,
                'message' => 'secret: value is required but missing',
                'data' => [],
            ]),
        ]);
        $this->actingAs($user);

        $response = app(ListAdvertisers::class)->handle(new Request([]));

        $this->assertStringContainsString('40002', (string) $response->content());
        $this->assertStringContainsString('secret: value is required but missing', (string) $response->content());
    }

    public function test_configured_advertiser_can_continue_when_tiktok_returns_no_advertisers(): void
    {
        $user = User::factory()->create();
        config(['services.tiktok.advertiser_id' => 'configured-advertiser']);
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => 'configured-advertiser',
        ]);
        Http::preventStrayRequests();
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/oauth2/advertiser/get/*' => Http::response([
                'data' => ['list' => []],
            ]),
        ]);

        $this->actingAs($user);
        $response = app(ListAdvertisers::class)->handle(new Request([]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertSame('configured-advertiser', $payload['selected_advertiser']['advertiser_id']);
        $this->assertFalse($payload['selected_advertiser']['verified']);
        $this->assertSame([], app(Choices::class)->all());
    }

    public function test_connected_user_can_start_campaign_setup_after_account_connection(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
            'advertiser_name' => 'Admedia TikTok',
            'advertisers' => [
                ['advertiser_id' => '7452605154932572161', 'advertiser_name' => 'Admedia TikTok'],
            ],
        ]);

        $this->actingAs($user)
            ->get(route('tiktokchat.show'))
            ->assertOk()
            ->assertSee('Choose objective')
            ->assertSee('Admedia TikTok')
            ->assertDontSee('Connect TikTok Ads');
    }

    public function test_connected_user_must_select_an_authorized_advertiser_before_campaign_objectives(): void
    {
        $user = User::factory()->create();
        config(['services.tiktok.advertiser_id' => null]);
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => '7452605154932572161',
        ]);

        $this->actingAs($user)
            ->get(route('tiktokchat.show'))
            ->assertOk()
            ->assertSee('List advertiser accounts')
            ->assertDontSee('Choose objective');
    }

    public function test_oauth_callback_persists_connection_for_future_visits(): void
    {
        $user = User::factory()->create();
        config(['services.tiktok.advertiser_id' => '7452605154932572161']);
        Http::fake([
            'business-api.tiktok.com/*' => Http::response([
                'data' => [
                    'access_token' => 'stored-access-token',
                    'refresh_token' => 'stored-refresh-token',
                    'open_id' => 'open-id',
                    'expires_in' => 86400,
                ],
            ]),
        ]);

        $response = $this->actingAs($user)
            ->withSession(['tiktok.oauth_state' => 'oauth-state'])
            ->get(route('tiktok.oauth.callback', ['state' => 'oauth-state', 'auth_code' => 'auth-code']));

        $response->assertRedirect(route('tiktokchat.show'));
        $this->assertDatabaseHas('tik_tok_connections', [
            'user_id' => $user->id,
            'advertiser_id' => '7452605154932572161',
        ]);
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/open_api/v1.3/oauth2/access_token/')
            && $request->hasHeader('Content-Type', 'application/json')
            && ($request->data()['auth_code'] ?? null) === 'auth-code'
            && ($request->data()['grant_type'] ?? null) === 'authorized_code'
        );

        $this->actingAs($user)
            ->get(route('tiktokchat.show'))
            ->assertSee('Choose objective')
            ->assertDontSee('Connect TikTok Ads');
    }

    public function test_oauth_callback_does_not_connect_when_tiktok_rejects_token_exchange(): void
    {
        $user = User::factory()->create();
        Http::preventStrayRequests();
        Http::fake([
            'https://business-api.tiktok.com/open_api/v1.3/oauth2/access_token/' => Http::response([
                'code' => 40002,
                'message' => 'Authorization code is invalid.',
                'data' => [],
            ]),
        ]);

        $response = $this->actingAs($user)
            ->withSession(['tiktok.oauth_state' => 'oauth-state'])
            ->get(route('tiktok.oauth.callback', ['state' => 'oauth-state', 'auth_code' => 'auth-code']));

        $response->assertRedirect(route('tiktokchat.show'))
            ->assertSessionHasErrors(['connection' => 'TikTok could not complete authorization.']);
        $this->assertDatabaseMissing('tik_tok_connections', ['user_id' => $user->id]);
    }

    public function test_user_can_only_select_advertisers_returned_for_their_tiktok_connection(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'advertiser_id' => null,
            'advertiser_name' => null,
            'advertisers' => [
                ['advertiser_id' => '1234567890', 'advertiser_name' => 'My TikTok Account'],
            ],
        ]);

        $this->actingAs($user);

        $denied = app(SelectAdvertiser::class)->handle(new Request([
            'advertiser_id' => '9999999999',
        ]));
        $this->assertStringContainsString('Choose a TikTok advertiser returned by TikTok.', (string) $denied->content());

        $accepted = app(SelectAdvertiser::class)->handle(new Request([
            'advertiser_id' => '1234567890',
        ]));
        $payload = json_decode((string) $accepted->content(), true);

        $this->assertTrue($payload['ok']);
        $this->assertSame('1234567890', $payload['advertiser_id']);
        $this->assertDatabaseHas('tik_tok_connections', [
            'user_id' => $user->id,
            'advertiser_id' => '1234567890',
            'advertiser_name' => 'My TikTok Account',
        ]);
    }

    public function test_tiktok_suggested_values_offer_alternatives_and_other(): void
    {
        $choices = app(Choices::class);
        $response = app(OfferCampaignOptions::class)->handle(new Request([
            'field' => 'campaign_name',
            'suggestion' => 'OfficeFace Buyer Guides - Traffic',
            'alternative' => 'OfficeFace Website Traffic',
            'alternative_2' => 'OfficeFace Buyer Guide Launch',
        ]));
        $payload = json_decode((string) $response->content(), true);
        $offered = $choices->drain();

        $this->assertTrue($payload['ok']);
        $this->assertSame(['OfficeFace Buyer Guides - Traffic', 'OfficeFace Website Traffic', 'OfficeFace Buyer Guide Launch', '__other__'], array_column($offered[0]['options'], 'value'));
    }

    public function test_tiktok_offers_budget_and_country_choices(): void
    {
        $choices = app(Choices::class);
        app(OfferCampaignOptions::class)->handle(new Request(['field' => 'daily_budget']));
        $budget = $choices->drain();
        app(OfferCampaignOptions::class)->handle(new Request(['field' => 'country']));
        $country = $choices->drain();

        $this->assertSame(['20', '50', '100', '__other__'], array_column($budget[0]['options'], 'value'));
        $this->assertSame(['US', 'CA', 'GB', '__other__'], array_column($country[0]['options'], 'value'));
    }

    public function test_tiktok_exposes_reference_objectives_and_destinations(): void
    {
        config(['services.tiktok.search_campaign_supported' => true]);
        $choices = app(Choices::class);
        app(OfferCampaignOptions::class)->handle(new Request(['field' => 'campaign_objective', 'objective' => 'traffic']));
        $objectives = $choices->drain();
        app(OfferCampaignOptions::class)->handle(new Request(['field' => 'campaign_type', 'objective' => 'traffic']));
        $campaignTypes = $choices->drain();
        app(OfferCampaignOptions::class)->handle(new Request(['field' => 'destination', 'objective' => 'lead_generation']));
        $destinations = $choices->drain();
        app(OfferCampaignOptions::class)->handle(new Request(['field' => 'frequency_cap', 'objective' => 'reach']));
        $frequencyCaps = $choices->drain();

        $this->assertContains('reach', array_column($objectives[0]['options'], 'value'));
        $this->assertContains('app_promotion', array_column($objectives[0]['options'], 'value'));
        $this->assertNotContains('search_campaign', array_column($objectives[0]['options'], 'value'));
        $this->assertContains('search_campaign', array_column($campaignTypes[0]['options'], 'value'));
        $this->assertContains('website', array_column($destinations[0]['options'], 'value'));
        $this->assertContains('instant_form', array_column($destinations[0]['options'], 'value'));
        $this->assertNotContains('tiktok_shop', array_column($destinations[0]['options'], 'value'));
        $this->assertContains('2', array_column($frequencyCaps[0]['options'], 'value'));
        $this->assertContains('frequency_cap', array_column(app(TikTokCampaignFlow::class)->stages(['objective' => 'reach'])['ad_group']['fields'], 'key'));
    }

    public function test_tiktok_hides_search_campaign_when_support_is_not_enabled(): void
    {
        config(['services.tiktok.search_campaign_supported' => false]);
        $choices = app(Choices::class);
        app(OfferCampaignOptions::class)->handle(new Request(['field' => 'campaign_type', 'objective' => 'reach']));

        $this->assertNotContains('search_campaign', array_column($choices->all()[0]['options'], 'value'));
    }

    public function test_connected_pixels_are_offered_as_selectable_options_on_a_fresh_flow(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => 'advertiser-1',
        ]);
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/pixel/list/*' => Http::response([
                'code' => 0,
                'data' => ['list' => [
                    ['pixel_id' => 'pixel-1', 'pixel_name' => 'Website Pixel'],
                    ['pixel_id' => 'pixel-2', 'pixel_name' => 'Checkout Pixel'],
                ]],
            ]),
        ]);
        $this->actingAs($user);

        app(OfferCampaignOptions::class)->handle(new Request(['field' => 'pixel_id']));
        $options = app(Choices::class)->drain();

        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_contains($request->url(), '/pixel/list/')
            && str_contains($request->url(), 'page_size=10'));
        $this->assertSame('pixel-1', $options[0]['options'][0]['value']);
        $this->assertSame('Website Pixel (pixel-1)', $options[0]['options'][0]['label']);
    }

    public function test_one_connected_pixel_is_auto_selected(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => 'advertiser-1',
        ]);
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/pixel/list/*' => Http::response([
                'code' => 0,
                'data' => ['list' => [['pixel_id' => 'pixel-only', 'pixel_name' => 'Purchase Pixel']]],
            ]),
        ]);
        $this->actingAs($user);

        $response = app(OfferCampaignOptions::class)->handle(new Request(['field' => 'pixel_id']));
        $payload = json_decode((string) $response->content(), true);

        $this->assertTrue($payload['auto_selected']);
        $this->assertSame('pixel-only', $payload['value']);
        $this->assertSame([], app(Choices::class)->all());
    }

    public function test_completed_spark_website_draft_validates_for_paused_remote_creation(): void
    {
        $user = User::factory()->create();
        $connection = TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => 'advertiser-1',
            'identity_id' => 'identity-1',
            'identity_type' => 'BC_AUTH_TT',
            'identity_authorized_bc_id' => 'bc-1',
            'tiktok_item_id' => 'post-1',
        ]);
        $conversationId = (string) Str::uuid7();
        DB::table('agent_conversations')->insert([
            'id' => $conversationId,
            'participant_type' => $user->getMorphClass(),
            'participant_id' => $user->getKey(),
            'chat_type' => 'tiktok_ads',
            'title' => 'Campaign preview',
            'created_at' => now(),
            'updated_at' => now(),
        ]);
        $this->actingAs($user);
        $draft = [];
        $values = [
            'objective' => 'traffic',
            'campaign_name' => 'Traffic campaign',
            'campaign_type' => 'manual',
            'promotion_type' => 'website',
            'optimization_location' => 'website',
            'optimization_goal' => 'click',
            'campaign_budget_optimization' => 'off',
            'placement' => 'tiktok',
            'targeting_mode' => 'automatic',
            'location_id' => 'US',
            'daily_budget' => '20',
            'schedule' => 'continuous',
            'bidding_strategy' => 'lowest_cost',
            'adgroup_optimization_goal' => 'click',
            'creative_source' => 'tiktok_post',
            'creative_format' => 'single_video',
            'ad_text' => 'Visit our site',
            'call_to_action' => 'LEARN_MORE',
            'landing_page_url' => 'https://example.com',
            'tracking_enabled' => 'no',
        ];
        while (($next = app(TikTokCampaignFlow::class)->next([
            ...$draft,
            'advertiser_id' => $connection->advertiser_id,
            'identity_id' => $connection->identity_id,
            'identity_mode' => 'spark',
            'tiktok_item_id' => $connection->tiktok_item_id,
        ])) !== null && $next['field'] !== 'validation_status') {
            $draft[$next['field']] = $values[$next['field']] ?? 'configured-value';
        }
        DB::table('agent_conversations')->where('id', $conversationId)->update(['tiktok_campaign_draft' => json_encode($draft)]);
        request()->merge(['conversation' => $conversationId]);

        $response = app(ValidateTikTokCampaignDraft::class)->handle(new Request(['conversation_id' => $conversationId]));
        $payload = json_decode((string) $response->content(), true);

        $this->assertTrue($payload['ok']);
        $this->assertTrue($payload['remote_creation_supported']);
        $this->assertSame('passed', $payload['validation_status']);
        $this->assertNull($payload['next']);
        $this->assertSame('passed', json_decode((string) DB::table('agent_conversations')->where('id', $conversationId)->value('tiktok_campaign_draft'), true)['validation_status']);
    }

    public function test_created_tiktok_campaign_can_be_kept_paused_or_published(): void
    {
        $user = User::factory()->create();
        TikTokConnection::factory()->for($user)->create([
            'access_token' => 'test-token',
            'advertiser_id' => 'advertiser-1',
        ]);
        Http::fake([
            'business-api.tiktok.com/open_api/v1.3/campaign/status/update/' => Http::response(['code' => 0, 'data' => []]),
        ]);
        $conversationId = (string) Str::uuid7();
        DB::table('agent_conversations')->insert([
            'id' => $conversationId,
            'participant_type' => $user->getMorphClass(),
            'participant_id' => $user->getKey(),
            'chat_type' => 'tiktok_ads',
            'title' => 'Created campaign',
            'tiktok_campaign_draft' => json_encode([
                'created_campaign_id' => 'campaign-1',
                'created_adgroup_id' => 'adgroup-1',
                'created_ad_id' => 'ad-1',
                'api_status' => 'paused',
            ]),
            'created_at' => now(),
            'updated_at' => now(),
        ]);
        $this->actingAs($user);

        $keptPaused = app(FinalizeTikTokCampaign::class)->handle(new Request([
            'conversation_id' => $conversationId,
            'action' => 'keep_paused',
        ]));
        $payload = json_decode((string) $keptPaused->content(), true);

        $this->assertTrue($payload['ok']);
        $this->assertSame('paused', $payload['api_status']);
        $published = json_decode((string) app(FinalizeTikTokCampaign::class)->handle(new Request([
            'conversation_id' => $conversationId,
            'action' => 'publish',
        ]))->content(), true);
        $this->assertTrue($published['ok']);
        $this->assertSame('enabled', $published['api_status']);
        Http::assertSent(fn (\Illuminate\Http\Client\Request $request): bool => str_ends_with($request->url(), '/campaign/status/update/')
            && data_get($request->data(), 'campaign_ids') === ['campaign-1']
            && data_get($request->data(), 'operation_status') === 'ENABLE');
    }

    public function test_tiktok_chat_fallback_offers_country_choices_when_agent_asks_in_prose(): void
    {
        $controller = app(TikTokChatController::class);
        $method = new ReflectionMethod($controller, 'offerFallbackChoices');
        $method->invoke($controller, 'Which countries or regions should the campaign target?');
        $choices = app(Choices::class)->all();

        $this->assertSame('country', $choices[0]['field']);
        $this->assertSame(['US', 'CA', 'GB', '__other__'], array_column($choices[0]['options'], 'value'));
    }

    public function test_unauthenticated_users_are_redirected_from_tiktok_chat(): void
    {
        $this->get(route('tiktokchat.show'))->assertRedirect(route('login'));
        $this->post(route('tiktokchat.send'), ['message' => 'hello'])->assertRedirect(route('login'));
    }

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

        $this->actingAs($user)
            ->get(route('tiktokchat.new'))
            ->assertRedirect(route('tiktokchat.show', ['new' => 1]));
    }
}
