<?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;

/**
 * When the campaign runs is one answer for the brief, asked once.
 *
 * The last of the fields that SHARED_WITH_BRIEF promised and nobody wrote, and
 * the only one that could not simply be written.
 *
 * A null date has two meanings: never asked, and asked and answered "no end
 * date". The brief fallback fires on blank() and cannot tell them apart, so
 * populating briefs.ends_at would have made a campaign deliberately set to run
 * continuously inherit an end date nobody chose and stop early. That is worse
 * than the repetition it would have fixed.
 *
 * So the dates left the fallback entirely and are applied here instead: copied
 * onto the campaign and recorded, so the campaign holds a real value and the
 * ambiguity never arises.
 */
class TheScheduleIsAskedOnceForTheBriefTest extends TestCase
{
    use RefreshDatabase;

    protected Brief $brief;

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

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

        app(Workspace::class)->bindTo('conv-shared-schedule');

        $this->brief = Brief::create([
            'conversation_id' => 'conv-shared-schedule',
            'user_id' => auth()->id(),
        ]);
    }

    private function campaign(string $platform): Campaign
    {
        $campaign = Campaign::create([
            'user_id' => auth()->id(),
            'brief_id' => $this->brief->id,
            'name' => ucfirst($platform).' campaign',
            'platform' => $platform,
            'status' => 'draft',
            'budget_mode' => 'daily',
        ]);

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

        return $campaign;
    }

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

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

    /** The defect: the answer never left the campaign that gave it. */
    public function test_the_dates_reach_the_brief(): void
    {
        $this->campaign('meta');

        $this->schedule(['starts_at' => '2026-10-01T00:00:00Z']);

        $this->assertSame('2026-10-01', $this->brief->refresh()->starts_at?->toDateString());
    }

    /** So the next platform is given them rather than asked. */
    public function test_the_next_platform_is_not_asked_again(): void
    {
        $this->campaign('meta');
        $this->schedule(['starts_at' => '2026-10-01T00:00:00Z']);

        $linkedin = $this->campaign('linkedin');
        $result = $this->schedule();

        $this->assertTrue($result['from_brief'] ?? false, 'LinkedIn was asked for the dates all over again.');
        $this->assertSame('2026-10-01', $linkedin->refresh()->starts_at?->toDateString());
    }

    /** The first platform is still asked, because nothing has been decided. */
    public function test_the_first_platform_is_still_asked(): void
    {
        $this->campaign('meta');

        $result = $this->schedule();

        $this->assertArrayNotHasKey('from_brief', $result);
        $this->assertStringContainsString('on screen', $result['note']);
    }

    /**
     * A campaign that answered for itself keeps its answer.
     *
     * Including when the answer was "no end date", which is a null and the
     * whole reason the brief fallback could not be used for this.
     */
    public function test_a_campaign_that_chose_to_run_continuously_is_left_alone(): void
    {
        $this->campaign('meta');
        $this->schedule(['starts_at' => '2026-10-01T00:00:00Z', 'ends_at' => '2026-10-20T00:00:00Z']);

        $linkedin = $this->campaign('linkedin');
        $this->schedule(['ends_at' => 'continuous']);

        // Asked again, it must not be handed the brief's end date.
        $result = $this->schedule();

        $this->assertArrayNotHasKey('from_brief', $result);
        $this->assertNull($linkedin->refresh()->ends_at);
    }

    /** The brief keeps the first answer rather than the last. */
    public function test_a_later_campaign_does_not_redefine_the_brief(): void
    {
        $this->campaign('meta');
        $this->schedule(['starts_at' => '2026-10-01T00:00:00Z']);

        $this->campaign('linkedin');
        $this->schedule(['starts_at' => '2026-12-25T00:00:00Z']);

        $this->assertSame('2026-10-01', $this->brief->refresh()->starts_at?->toDateString());
    }

    /**
     * A brief with no dates at all shares nothing.
     *
     * "Continuous, starting whenever" is two nulls, which reads exactly like a
     * brief nobody has answered, so the later platform is asked rather than
     * handed an answer that might not be one.
     */
    public function test_a_brief_answered_with_no_dates_at_all_still_asks(): void
    {
        $this->campaign('meta');
        $this->schedule(['ends_at' => 'continuous']);

        $this->campaign('google');
        $result = $this->schedule();

        $this->assertArrayNotHasKey('from_brief', $result);
    }

    /**
     * Inheriting a start does not answer the end date.
     *
     * Applying both fields and recording both marked the end date as answered
     * when the brief held only a start, so the step completed and nothing asked
     * for one. On a lifetime budget the gate then refuses the publish because a
     * lifetime total needs an end date, and no step is left to supply it. Found
     * reviewing this change, not by running it.
     */
    public function test_inheriting_a_start_leaves_the_end_date_still_to_answer(): void
    {
        $this->campaign('meta');
        $this->schedule(['starts_at' => '2026-10-01T00:00:00Z']);

        $linkedin = Campaign::create([
            'user_id' => auth()->id(),
            'brief_id' => $this->brief->id,
            'name' => 'LinkedIn campaign',
            'platform' => 'linkedin',
            'status' => 'draft',
            'budget_mode' => 'lifetime',
        ]);
        app(Workspace::class)->focusOn($linkedin);

        $this->schedule();

        $this->assertArrayNotHasKey('ends_at', app(Workspace::class)->sourcesFor($linkedin->refresh()),
            'the end date was marked answered without being asked, which deadlocks a lifetime budget');
        $this->assertSame('2026-10-01', $linkedin->starts_at?->toDateString());
    }

    // --------------------------------------------------- the fallback itself

    /** The three fields that were promised and could not be delivered safely. */
    public function test_the_dates_and_currency_are_not_in_the_brief_fallback(): void
    {
        foreach (['currency', 'starts_at', 'ends_at'] as $field) {
            $this->assertNotContains($field, Campaign::SHARED_WITH_BRIEF,
                "{$field} is back in the fallback, where null is ambiguous or the value belongs to the ad account");
        }
    }

    /**
     * Currency belongs to the ad account, not the brief.
     *
     * BudgetCeiling::for() reads it to decide which ceiling applies, so a
     * campaign inheriting another platform's currency would be measured against
     * the wrong limit on somebody's money.
     */
    public function test_a_campaign_does_not_inherit_a_currency(): void
    {
        $this->brief->update(['currency' => 'EUR']);

        $campaign = $this->campaign('meta');

        // The campaign keeps its own default rather than the brief's EUR. The
        // point is that the brief never reaches it, not that it holds nothing.
        $this->assertNotSame('EUR', $campaign->refresh()->currency);
    }
}
