<?php

namespace Tests\Tenant;

use App\Events\StockSyncFinished;
use App\Jobs\RunScheduledTaskJob;
use App\ScheduledTasks\ScheduledTaskRunner;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Mtc\MercuryDataModels\ScheduledTask;
use Mtc\MercuryDataModels\ScheduledTaskAction;
use Mtc\MercuryDataModels\ScheduledTaskRun;
use Tests\Support\FakeTaskAction;
use Tests\TenantTestCase;

class ScheduledTaskChainingTest extends TenantTestCase
{
    #[\Override]
    protected function setUp(): void
    {
        parent::setUp();
        config()->set('scheduled_tasks.actions.fake', FakeTaskAction::class);
        FakeTaskAction::$executed = [];
    }

    public function testFanOutChildrenFirePerTheirConditionAfterParentSuccess(): void
    {
        Queue::fake();

        $parent = ScheduledTask::factory()->create(['name' => 'Parent']);
        ScheduledTaskAction::factory()->create(['scheduled_task_id' => $parent->id, 'action_type' => 'fake']);

        $onSuccess = ScheduledTask::factory()->afterTask($parent)->create(['name' => 'On success child']);
        $onFailure = ScheduledTask::factory()
            ->afterTask($parent, ScheduledTask::CONDITION_ON_FAILURE)
            ->create(['name' => 'On failure child']);
        $always = ScheduledTask::factory()
            ->afterTask($parent, ScheduledTask::CONDITION_ALWAYS)
            ->create(['name' => 'Always child']);

        $run = ScheduledTaskRun::factory()->create(['scheduled_task_id' => $parent->id]);
        app(ScheduledTaskRunner::class)->execute($run);

        // Every child gets a linked run row (fan-out); condition decides dispatch vs skipped
        $this->assertEquals(1, $onSuccess->runs()->count());
        $this->assertEquals(1, $onFailure->runs()->count());
        $this->assertEquals(1, $always->runs()->count());

        $this->assertEquals(ScheduledTaskRun::STATUS_PENDING, $onSuccess->runs()->first()->status);
        $this->assertEquals(ScheduledTaskRun::STATUS_SKIPPED, $onFailure->runs()->first()->status);
        $this->assertStringContainsString('Parent succeeded', $onFailure->runs()->first()->error);
        $this->assertEquals(ScheduledTaskRun::STATUS_PENDING, $always->runs()->first()->status);

        $this->assertEquals($run->id, $onSuccess->runs()->first()->triggered_by_run_id);
        Queue::assertPushed(RunScheduledTaskJob::class, 2);
    }

    public function testOnFailureChildFiresWhenParentFails(): void
    {
        Queue::fake();

        $parent = ScheduledTask::factory()->create();
        ScheduledTaskAction::factory()->create([
            'scheduled_task_id' => $parent->id,
            'action_type' => 'fake',
            'config' => ['fail' => true],
        ]);

        $onSuccess = ScheduledTask::factory()->afterTask($parent)->create();
        $onFailure = ScheduledTask::factory()
            ->afterTask($parent, ScheduledTask::CONDITION_ON_FAILURE)
            ->create();

        $run = ScheduledTaskRun::factory()->create(['scheduled_task_id' => $parent->id]);
        app(ScheduledTaskRunner::class)->execute($run);

        $this->assertEquals(ScheduledTaskRun::STATUS_SKIPPED, $onSuccess->runs()->first()->status);
        $this->assertEquals(ScheduledTaskRun::STATUS_PENDING, $onFailure->runs()->first()->status);
        Queue::assertPushed(RunScheduledTaskJob::class, 1);
    }

    public function testRuntimeGuardRefusesLoopedChains(): void
    {
        Queue::fake();

        $parent = ScheduledTask::factory()->create();
        ScheduledTaskAction::factory()->create(['scheduled_task_id' => $parent->id, 'action_type' => 'fake']);

        // Corrupted graph: the parent is its own child
        $parent->update([
            'trigger_type' => ScheduledTask::TRIGGER_AFTER_TASK,
            'parent_task_id' => $parent->id,
        ]);

        $run = ScheduledTaskRun::factory()->create(['scheduled_task_id' => $parent->id]);
        app(ScheduledTaskRunner::class)->execute($run);

        // The loop guard means no second run of the same task is ever created
        $this->assertEquals(1, $parent->runs()->count());
        Queue::assertNothingPushed();
    }

    public function testStockSyncEventTriggersSubscribedTasksHonouringProviderFilter(): void
    {
        Queue::fake();

        $anyProvider = ScheduledTask::factory()->create([
            'trigger_type' => ScheduledTask::TRIGGER_AFTER_EVENT,
            'event' => 'stock-sync-finished',
            'event_filter' => null,
            'schedule_rules' => null,
        ]);
        $autoTraderOnly = ScheduledTask::factory()->create([
            'trigger_type' => ScheduledTask::TRIGGER_AFTER_EVENT,
            'event' => 'stock-sync-finished',
            'event_filter' => 'auto-trader',
            'schedule_rules' => null,
        ]);
        $paused = ScheduledTask::factory()->create([
            'enabled' => false,
            'trigger_type' => ScheduledTask::TRIGGER_AFTER_EVENT,
            'event' => 'stock-sync-finished',
            'schedule_rules' => null,
        ]);

        // Invoke the listener directly: dispatching the real event would also fire
        // the other nine production listeners, which need infrastructure this test does not
        $listener = app(\App\Listeners\TriggerScheduledTasksOnPlatformEvent::class);

        $listener->handle(new StockSyncFinished('skupenet'));

        $this->assertEquals(1, $anyProvider->runs()->count());
        $this->assertEquals(0, $autoTraderOnly->runs()->count());
        $this->assertEquals(0, $paused->runs()->count());

        $listener->handle(new StockSyncFinished('auto-trader'));

        $this->assertEquals(2, $anyProvider->runs()->count());
        $this->assertEquals(1, $autoTraderOnly->runs()->count());
        $this->assertEquals(
            ScheduledTaskRun::TRIGGER_AFTER_EVENT,
            $autoTraderOnly->runs()->first()->trigger
        );
    }
}
