<?php

namespace Tests\Feature;

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

/**
 * The agent core: a conversation is a workspace, not a campaign.
 *
 * These cover the three behaviours everything else is built on, so that adding
 * creative, landing page or analysis tools cannot quietly break them.
 */
class AgentWorkspaceTest extends TestCase
{
    use RefreshDatabase;

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

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

    public function test_one_conversation_can_hold_several_campaigns(): void
    {
        $first = $this->runTool('campaign.start_campaign', ['name' => 'Winter Sale', 'platform' => 'meta']);
        $second = $this->runTool('campaign.start_campaign', ['name' => 'Lead Gen Q4', 'platform' => 'google']);

        $this->assertTrue($first['ok']);
        $this->assertTrue($second['ok']);
        $this->assertNotSame($first['campaign_id'], $second['campaign_id']);

        $this->assertSame(2, ConversationArtifact::where('conversation_id', 'conv-test-0001')->count());
    }

    public function test_creating_a_campaign_focuses_it_and_edits_land_on_the_right_row(): void
    {
        $first = $this->runTool('campaign.start_campaign', ['name' => 'Winter Sale', 'platform' => 'meta']);
        $second = $this->runTool('campaign.start_campaign', ['name' => 'Lead Gen Q4', 'platform' => 'google']);

        // The most recently created campaign is in focus, so this budget is its.
        $this->assertTrue($this->runTool('campaign.set_campaign_budget', ['daily_budget' => 15])['ok']);

        $this->runTool('workspace.switch_focus', ['kind' => 'Campaign', 'id' => $first['campaign_id']]);
        $this->assertTrue($this->runTool('campaign.set_campaign_budget', ['daily_budget' => 20])['ok']);

        $this->assertSame('20.00', Campaign::find($first['campaign_id'])->budget);
        $this->assertSame('15.00', Campaign::find($second['campaign_id'])->budget);
    }

    public function test_a_budget_above_the_ceiling_is_refused(): void
    {
        config(['agent.max_daily_budget' => 20]);

        $campaign = $this->runTool('campaign.start_campaign', ['name' => 'Winter Sale', 'platform' => 'meta']);
        $result = $this->runTool('campaign.set_campaign_budget', ['daily_budget' => 500]);

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('ceiling', $result['error']);
        $this->assertNull(Campaign::find($campaign['campaign_id'])->budget);
    }

    public function test_editing_with_nothing_in_focus_is_refused_rather_than_guessed(): void
    {
        $result = $this->runTool('campaign.set_campaign_budget', ['daily_budget' => 15]);

        $this->assertFalse($result['ok']);
        $this->assertStringContainsString('No campaign is in focus', $result['error']);
    }

    public function test_an_unsupported_platform_is_refused_with_the_supported_list(): void
    {
        $result = $this->runTool('campaign.start_campaign', ['name' => 'X', 'platform' => 'snapchat']);

        $this->assertFalse($result['ok']);
        $this->assertContains('meta', $result['supported']);
        $this->assertSame(0, Campaign::count());
    }

    public function test_switching_focus_to_something_outside_the_conversation_is_refused(): void
    {
        $this->runTool('campaign.start_campaign', ['name' => 'Winter Sale', 'platform' => 'meta']);

        $result = $this->runTool('workspace.switch_focus', ['kind' => 'Campaign', 'id' => 9999]);

        $this->assertFalse($result['ok']);
    }

    public function test_stated_and_inferred_values_are_recorded_separately(): void
    {
        $created = $this->runTool('campaign.start_campaign', [
            'name' => 'Winter Sale',
            'platform' => 'meta',
            'objective' => 'OUTCOME_SALES',
        ]);

        $sources = app(Workspace::class)->sourcesFor(Campaign::find($created['campaign_id']));

        $this->assertSame(ArtifactFieldSource::USER_STATED, $sources['name']);
        $this->assertSame(ArtifactFieldSource::USER_STATED, $sources['platform']);
        $this->assertSame(ArtifactFieldSource::MODEL_INFERRED, $sources['objective']);
    }

    public function test_an_omitted_optional_argument_does_not_error(): void
    {
        $result = $this->runTool('campaign.start_campaign', ['name' => 'No Objective', 'platform' => 'meta']);

        $this->assertTrue($result['ok']);
        $this->assertArrayNotHasKey('objective', app(Workspace::class)->sourcesFor(
            Campaign::find($result['campaign_id'])
        ));
    }

    public function test_tools_are_exposed_under_their_namespace(): void
    {
        $names = array_map(
            fn (Tool $tool): string => $tool->name(),
            app(ToolRegistry::class)->resolve(),
        );

        $this->assertContains('campaign.start_campaign', $names);
        $this->assertContains('workspace.switch_focus', $names);
    }

    /** @return array<string,mixed> */
    private function runTool(string $name, array $arguments): array
    {
        foreach (app(ToolRegistry::class)->resolve() as $tool) {
            if ($tool->name() === $name) {
                return json_decode((string) $tool->handle(new Request($arguments)), true);
            }
        }

        $this->fail("No tool registered as [{$name}].");
    }
}
