<?php

namespace Tests\Feature;

use App\Agent\Choices;
use App\Agent\MarketingAgent;
use App\Agent\ToolRegistry;
use App\Agent\Workspace;
use App\Campaigns\LaunchFlow;
use App\Campaigns\PublishGate;
use App\Models\Asset;
use App\Models\AssetPlatformRef;
use App\Models\Campaign;
use App\Models\CampaignAd;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Laravel\Ai\Tools\Request;
use Tests\TestCase;

class ChatLinkedInFlowTest extends TestCase
{
    use RefreshDatabase;

    protected User $user;

    protected Campaign $campaign;

    protected Workspace $workspace;

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

        MarketingAgent::fake(['Pick one.']);

        Config::set('platforms.guardrails.allowed_ad_accounts', [
            '556447014',
            'urn:li:sponsoredAccount:556447014',
        ]);
        Config::set('platforms.linkedin.access_token', 'test-token');
        Config::set('platforms.guardrails.max_daily_budget', 1000.0);

        $this->user = User::factory()->create();
        $this->actingAs($this->user);

        $this->workspace = app(Workspace::class);
        $this->workspace->bindTo('conv-linkedin-0001');
        $this->workspace->ownedBy($this->user);

        $this->campaign = Campaign::create([
            'user_id' => $this->user->id,
            'name' => 'LinkedIn B2B Campaign',
            'platform' => 'linkedin',
            'status' => 'draft',
            'objective' => 'WEBSITE_VISITS',
            'currency' => 'USD',
        ]);

        $this->workspace->focusOn($this->campaign);
    }

    private function runTool(string $name, array $arguments = []): string
    {
        $tool = collect(app(ToolRegistry::class)->resolve())->first(fn ($t) => $t->name() === $name);

        $this->assertNotNull($tool, "Tool {$name} is not registered.");

        return (string) $tool->handle(new Request($arguments));
    }

    public function test_list_ad_accounts_for_linkedin(): void
    {
        Http::fake([
            'https://api.linkedin.com/rest/adAccounts?q=search' => Http::response([
                'elements' => [
                    [
                        'id' => 556447014,
                        'name' => 'AdMedia Products',
                        'currency' => 'USD',
                        'status' => 'ACTIVE',
                    ],
                ],
            ], 200),
        ]);

        $result = $this->runTool('campaign__list_ad_accounts');
        $json = json_decode($result, true);

        $this->assertTrue($json['ok']);
        $this->assertCount(1, $json['ad_accounts']);
        $this->assertEquals('556447014', $json['ad_accounts'][0]['id']);

        $all = app(Choices::class)->all();
        $this->assertNotEmpty($all);
        $this->assertEquals('ad_account_id', $all[0]['field']);
        $this->assertEquals('556447014', $all[0]['options'][0]['value']);
    }

    public function test_verify_ad_account_for_linkedin(): void
    {
        Http::fake([
            'https://api.linkedin.com/rest/adAccounts/556447014' => Http::response([
                'id' => 556447014,
                'name' => 'AdMedia Products',
                'currency' => 'USD',
                'status' => 'ACTIVE',
            ], 200),
        ]);

        $result = $this->runTool('campaign__verify_ad_account', [
            'ad_account_id' => '556447014',
        ]);
        $json = json_decode($result, true);

        $this->assertTrue($json['ok']);
        $this->assertEquals('556447014', $json['ad_account_id']);

        $this->campaign->refresh();
        $this->assertEquals('556447014', $this->campaign->ad_account_id);
        $this->assertEquals('USD', $this->campaign->currency);
    }

    public function test_targeting_offers_and_accepts_linkedin_placements(): void
    {
        // 1. Calling without countries offers countries and LinkedIn placements
        $result = $this->runTool('campaign__set_targeting', []);
        $json = json_decode($result, true);
        $this->assertTrue($json['ok']);

        // 2. Setting country and linkedin placements
        $result = $this->runTool('campaign__set_targeting', [
            'countries' => ['US'],
            'placements' => ['linkedin_feed'],
        ]);
        $json = json_decode($result, true);

        $this->assertTrue($json['ok']);
        $this->assertEquals(['US'], $json['countries']);
        $this->assertEquals(['linkedin_feed'], $json['placements']);

        $this->campaign->refresh();
        $this->assertEquals(['countries' => ['US']], $this->campaign->locations);
        $this->assertEquals(['linkedin_feed'], $this->campaign->placements);
    }

    public function test_bidding_offers_and_accepts_linkedin_strategies(): void
    {
        // 1. Offering strategies for LinkedIn
        $result = $this->runTool('campaign__set_bidding', []);
        $json = json_decode($result, true);
        $this->assertTrue($json['ok']);

        $all = app(Choices::class)->all();
        $this->assertNotEmpty($all);
        $this->assertEquals('bid_strategy', $all[0]['field']);
        $values = array_column($all[0]['options'], 'value');
        $this->assertContains('AUTOMATED_BID', $values);
        $this->assertContains('TARGET_COST', $values);
        $this->assertContains('MAXIMUM_BID', $values);

        // 2. Setting AUTOMATED_BID
        $result = $this->runTool('campaign__set_bidding', [
            'strategy' => 'AUTOMATED_BID',
        ]);
        $json = json_decode($result, true);

        $this->assertTrue($json['ok']);
        $this->assertEquals('AUTOMATED_BID', $json['strategy']);

        $this->campaign->refresh();
        $this->assertEquals('AUTOMATED_BID', $this->campaign->bid_strategy);
    }

    public function test_launch_flow_skips_page_and_special_ad_categories_for_linkedin(): void
    {
        $flow = app(LaunchFlow::class);
        $progress = collect($flow->progress($this->campaign))->keyBy('key');

        $this->assertFalse($progress->has('page'), 'LinkedIn campaigns must not require Facebook Page');
        $this->assertFalse($progress->has('special_ad_categories'), 'LinkedIn campaigns must not require Special Ad Categories');
    }

    /**
     * A Facebook Page is Meta's requirement, not LinkedIn's.
     *
     * The ad is given a creative that has reached the platform, which it needs
     * anyway. This used to pass with no creative at all, because the gate
     * exempted LinkedIn from the creative check, and the assertion that the
     * Page is not required was quietly also asserting that an ad with nothing
     * to show is ready to publish. LinkedInRules::requiresCreativeAsset() says
     * otherwise, and the publisher had nothing to sponsor.
     */
    public function test_publish_gate_passes_linkedin_without_page_id(): void
    {
        $this->campaign->update([
            'ad_account_id' => '556447014',
            'landing_url' => 'https://example.com/product',
            'budget' => 50.00,
            'budget_mode' => 'daily',
            'bid_strategy' => 'AUTOMATED_BID',
            'locations' => ['countries' => ['US']],
            'placements' => ['linkedin_feed'],
        ]);

        $asset = Asset::create([
            'owner_type' => User::class,
            'owner_id' => $this->user->id,
            'kind' => 'image',
            'source' => 'upload',
            'disk' => 'local',
            'path' => 'creatives/test.png',
            'original_name' => 'test.png',
        ]);

        AssetPlatformRef::create([
            'asset_id' => $asset->id,
            'platform' => 'linkedin',
            'ad_account_id' => '556447014',
            'external_id' => 'urn:li:image:C4E10AQ',
        ]);

        CampaignAd::create([
            'campaign_id' => $this->campaign->id,
            'asset_id' => $asset->id,
            'headline' => 'Scale your growth today',
            'primary_text' => 'Discover how our platform accelerates revenue.',
            'cta' => 'LEARN_MORE',
        ]);

        $problems = app(PublishGate::class)->check($this->campaign);

        $this->assertEmpty($problems, 'Publish gate should pass without Facebook Page for LinkedIn: '.implode('; ', $problems));
    }

    /** And an ad with nothing to show is still refused. */
    public function test_publish_gate_refuses_a_linkedin_ad_with_no_creative(): void
    {
        $this->campaign->update([
            'ad_account_id' => '556447014',
            'landing_url' => 'https://example.com/product',
            'budget' => 50.00,
            'budget_mode' => 'daily',
            'bid_strategy' => 'AUTOMATED_BID',
            'locations' => ['countries' => ['US']],
            'placements' => ['linkedin_feed'],
        ]);

        CampaignAd::create([
            'campaign_id' => $this->campaign->id,
            'headline' => 'Scale your growth today',
            'primary_text' => 'Discover how our platform accelerates revenue.',
            'cta' => 'LEARN_MORE',
        ]);

        $this->assertStringContainsString(
            'creative not uploaded',
            implode('; ', app(PublishGate::class)->check($this->campaign)),
        );
    }

    public function test_review_assets_uploads_image_to_linkedin(): void
    {
        $this->campaign->update(['ad_account_id' => '556447014']);

        Storage::fake('local');
        Storage::disk('local')->put('creatives/test.png', 'fake-image-bytes');

        $asset = Asset::create([
            'owner_type' => User::class,
            'owner_id' => $this->user->id,
            'kind' => 'image',
            'source' => 'upload',
            'disk' => 'local',
            'path' => 'creatives/test.png',
            'original_name' => 'test.png',
            'mime' => 'image/png',
            'bytes' => 100,
            'checksum' => 'checksum123',
        ]);

        $this->workspace->produced($asset, focus: false);

        Http::fake([
            'https://api.linkedin.com/rest/images?action=initializeUpload' => Http::response([
                'value' => [
                    'uploadUrl' => 'https://api.linkedin.com/mock-upload-url',
                    'image' => 'urn:li:image:MOCK123456',
                ],
            ], 200),
            'https://api.linkedin.com/mock-upload-url' => Http::response('', 201),
        ]);

        $result = $this->runTool('campaign__review_assets');
        $json = json_decode($result, true);

        $this->assertTrue($json['ok']);
        $this->assertCount(1, $json['just_uploaded']);
        $this->assertEquals('urn:li:image:MOCK123456', $json['just_uploaded'][0]['reference']);

        $ref = $asset->refFor('linkedin', '556447014');
        $this->assertNotNull($ref);
        $this->assertEquals('urn:li:image:MOCK123456', $ref->reference());
    }
}
