<?php

namespace Tests\Feature;

use App\Agent\ToolRegistry;
use App\Agent\Workspace;
use App\Models\Brief;
use App\Models\Campaign;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Ai\Tools\Request;
use Tests\TestCase;

/**
 * Four failures counted across every conversation on arb-dev.
 *
 * 4142 messages, 192 conversations, 185 tool refusals in 76 distinct shapes.
 * These are the four that users actually hit, with the real payloads and the
 * real counts, rather than the ones that were easiest to imagine.
 */
class WhatTheStoredConversationsShowedTest extends TestCase
{
    use RefreshDatabase;

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

        $this->actingAs(User::factory()->create());
        app(Workspace::class)->bindTo('conv-stored');
    }

    private function campaign(string $platform = 'meta', array $attributes = []): Campaign
    {
        $brief = Brief::create([
            'conversation_id' => 'conv-stored',
            'user_id' => auth()->id(),
            'landing_url' => 'https://iblockads.org/',
        ]);

        $campaign = Campaign::create([
            'user_id' => auth()->id(),
            'brief_id' => $brief->id,
            'name' => ucfirst($platform).' campaign',
            'platform' => $platform,
            'status' => 'draft',
            'landing_url' => 'https://iblockads.org/',
            ...$attributes,
        ]);

        app(Workspace::class)->focusOn($campaign);

        return $campaign;
    }

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

        return json_decode((string) $result, true);
    }

    // ------------------------------------------- 1: ad copy arriving mangled

    /**
     * The payload is named as broken JSON, not as a bad call to action.
     *
     * Verbatim from a stored conversation. The model put its own delimiters
     * inside the values, this refused with "[}]}] is not a supported call to
     * action", and it then retried twelve times in one turn, each attempt
     * corrupted differently. Nine refusals of this shape across the database.
     */
    public function test_ad_copy_that_arrived_as_broken_json_says_so(): void
    {
        $this->campaign();

        $result = $this->tool('campaign__write_ad_copy', ['ads' => [
            [
                'headline' => 'iBlockAds AdBlocker',
                'primary_text' => 'Block pop-ups, banners, and annoying ads for smoother web browsing daily.},{cta:',
                'cta' => 'LEARN_MORE',
                'description' => 'Install iblockads to block annoying ads and browse faster.',
            ],
        ]]);

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('valid JSON', $result['error']);
        $this->assertStringNotContainsString(
            'call to action',
            $result['error'],
            'the refusal still blames the CTA when the whole payload is malformed',
        );
    }

    /** The other shape from the same turns: a field name as a value. */
    public function test_a_field_name_used_as_a_value_is_caught(): void
    {
        $this->campaign();

        $result = $this->tool('campaign__write_ad_copy', ['ads' => [
            ['headline' => ',primary_text:', 'primary_text' => ',primary_text:', 'cta' => ',description:'],
        ]]);

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('valid JSON', $result['error']);
    }

    /** It tells the model not to grind: one retry, then ask the user. */
    public function test_the_refusal_caps_the_retrying(): void
    {
        $this->campaign();

        $result = $this->tool('campaign__write_ad_copy', ['ads' => [
            ['headline' => 'A}]}', 'primary_text' => 'B', 'cta' => 'LEARN_MORE'],
        ]]);

        $this->assertStringContainsString('once', strtolower($result['note']));
    }

    /** Ordinary copy with punctuation is not mistaken for corruption. */
    public function test_ordinary_copy_still_passes(): void
    {
        $this->campaign('meta', ['objective' => 'OUTCOME_TRAFFIC']);

        $result = $this->tool('campaign__write_ad_copy', ['ads' => [
            [
                'headline' => 'Block ads, browse faster',
                'primary_text' => 'Stop pop-ups: banners, video ads and more. Free, and it works everywhere!',
                'cta' => 'LEARN_MORE',
                'description' => 'Install in seconds (no account needed).',
            ],
        ]]);

        $this->assertTrue($result['ok'], $result['error'] ?? '');
    }

    // ------------------------------------ 3: refusals that gave no reason

    /**
     * request_publish says why it refused.
     *
     * Thirty refusals across the database returned ok, ready and still_needed
     * with no error key at all, so the model had nothing to relay and the buyer
     * saw a publish that did not happen and no reason for it.
     */
    public function test_a_publish_refusal_carries_a_reason(): void
    {
        $this->campaign();

        $result = $this->tool('campaign__request_publish');

        $this->assertFalse($result['ok']);
        $this->assertNotEmpty($result['error'] ?? '', 'the refusal had no error to relay');
        $this->assertNotEmpty($result['still_needed'] ?? []);
    }

    // --------------------------- 4: a Meta objective on another platform

    /**
     * An objective the platform does not have is not written.
     *
     * Nine refusals across the database, all arriving a step or two after the
     * objective was set: "[OUTCOME_TRAFFIC] is not supported on Google Ads".
     * Worse than the refusal, LaunchFlow::done() only asks whether the field is
     * filled, so the step was marked answered and never re-asked.
     */
    public function test_a_meta_objective_is_not_written_to_a_google_campaign(): void
    {
        $result = $this->tool('campaign__start_campaign', [
            'name' => 'iBlockAds',
            'platform' => 'google',
            'objective' => 'OUTCOME_TRAFFIC',
        ]);

        $campaign = Campaign::find($result['campaign_id'] ?? 0);

        $this->assertNotNull($campaign);
        $this->assertNull(
            $campaign->objective,
            'a Meta objective was written to a Google campaign, and the step is now marked answered',
        );
    }

    /** The platform's own objective is still accepted. */
    public function test_a_google_objective_is_written_to_a_google_campaign(): void
    {
        $result = $this->tool('campaign__start_campaign', [
            'name' => 'iBlockAds',
            'platform' => 'google',
            'objective' => 'WEBSITE_TRAFFIC',
        ]);

        $this->assertSame('WEBSITE_TRAFFIC', Campaign::find($result['campaign_id'])->objective);
    }

    /** And Meta keeps its own. */
    public function test_a_meta_objective_is_written_to_a_meta_campaign(): void
    {
        $result = $this->tool('campaign__start_campaign', [
            'name' => 'iBlockAds',
            'platform' => 'meta',
            'objective' => 'OUTCOME_TRAFFIC',
        ]);

        $this->assertSame('OUTCOME_TRAFFIC', Campaign::find($result['campaign_id'])->objective);
    }
}
