<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_runtime\Unit\Service\Storage;

use Drupal\flowdrop_job\FlowDropJobInterface;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\flowdrop_runtime\Service\Storage\InMemoryPipeline;
use Drupal\flowdrop_runtime\Service\Storage\InMemoryPipelineStorage;
use Prophecy\PhpUnit\ProphecyTrait;

/**
 * Tests InMemoryPipeline value object behaviour.
 *
 * @coversDefaultClass \Drupal\flowdrop_runtime\Service\Storage\InMemoryPipeline
 * @group flowdrop_runtime
 */
class InMemoryPipelineTest extends UnitTestCase {

  use ProphecyTrait;

  /**
   * Helper: create a pipeline via storage so ID/UUID are properly assigned.
   */
  private function makePipeline(array $data = [], string $fixedUuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'): InMemoryPipeline {
    $uuid = $this->prophesize(UuidInterface::class);
    $uuid->generate()->willReturn($fixedUuid);
    $storage = new InMemoryPipelineStorage($uuid->reveal());
    return $storage->create($data);
  }

  // ---------------------------------------------------------------------------
  // Constructor guards
  // ---------------------------------------------------------------------------

  /**
   * @covers ::__construct
   */
  public function testConstructorThrowsWithoutId(): void {
    $this->expectException(\InvalidArgumentException::class);
    new InMemoryPipeline([]);
  }

  // ---------------------------------------------------------------------------
  // Static create() is disabled
  // ---------------------------------------------------------------------------

  /**
   * @covers ::create
   */
  public function testStaticCreateThrowsBadMethodCallException(): void {
    $this->expectException(\BadMethodCallException::class);
    InMemoryPipeline::create(['id' => 'test']);
  }

  // ---------------------------------------------------------------------------
  // ID and UUID
  // ---------------------------------------------------------------------------

  /**
   * @covers ::id
   */
  public function testIdReturnsPrefixedString(): void {
    $pipeline = $this->makePipeline();
    $this->assertSame('inmem_aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', $pipeline->id());
  }

  /**
   * @covers ::uuid
   */
  public function testUuidReturnsBareFourPartUuid(): void {
    $pipeline = $this->makePipeline([], 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee');
    // uuid() must return the raw UUID, not the prefixed ID.
    $this->assertSame('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', $pipeline->uuid());
    $this->assertNotSame($pipeline->id(), $pipeline->uuid());
  }

  // ---------------------------------------------------------------------------
  // Status transitions
  // ---------------------------------------------------------------------------

  /**
   * @covers ::isReadyToStart
   * @covers ::markAsStarted
   * @covers ::isRunning
   */
  public function testPendingToRunningTransition(): void {
    $pipeline = $this->makePipeline();
    $this->assertTrue($pipeline->isReadyToStart());

    $pipeline->markAsStarted();
    $this->assertTrue($pipeline->isRunning());
    $this->assertFalse($pipeline->isReadyToStart());
  }

  /**
   * @covers ::markAsCompleted
   * @covers ::isCompleted
   */
  public function testMarkAsCompleted(): void {
    $pipeline = $this->makePipeline();
    $pipeline->markAsStarted();
    $pipeline->markAsCompleted(['result' => 'ok']);

    $this->assertTrue($pipeline->isCompleted());
    $this->assertSame(['result' => 'ok'], $pipeline->getOutputData());
  }

  /**
   * @covers ::markAsFailed
   * @covers ::isFailed
   */
  public function testMarkAsFailed(): void {
    $pipeline = $this->makePipeline();
    $pipeline->markAsFailed('something went wrong');

    $this->assertTrue($pipeline->isFailed());
    $this->assertSame('something went wrong', $pipeline->getErrorMessage());
  }

  /**
   * @covers ::pause
   * @covers ::isPaused
   * @covers ::resume
   */
  public function testPauseAndResume(): void {
    $pipeline = $this->makePipeline();
    $pipeline->markAsStarted();
    $pipeline->pause();

    $this->assertTrue($pipeline->isPaused());

    $pipeline->resume();
    $this->assertTrue($pipeline->isRunning());
  }

  // ---------------------------------------------------------------------------
  // Job management
  // ---------------------------------------------------------------------------

  /**
   * @covers ::addJob
   * @covers ::getJobs
   * @covers ::getJobsByStatus
   * @covers ::hasFailedJobs
   */
  public function testJobManagement(): void {
    $pipeline = $this->makePipeline();

    $job = $this->prophesize(FlowDropJobInterface::class);
    $job->id()->willReturn('job-1');
    $job->getStatus()->willReturn('pending');

    $pipeline->addJob($job->reveal());

    $this->assertCount(1, $pipeline->getJobs());
    $this->assertCount(1, $pipeline->getJobsByStatus('pending'));
    $this->assertCount(0, $pipeline->getJobsByStatus('completed'));
    $this->assertFalse($pipeline->hasFailedJobs());
  }

  /**
   * @covers ::removeJob
   */
  public function testRemoveJobDeletesFromCollection(): void {
    $pipeline = $this->makePipeline();

    $job = $this->prophesize(FlowDropJobInterface::class);
    $job->id()->willReturn('job-1');
    $job->getStatus()->willReturn('pending');

    $pipeline->addJob($job->reveal());
    $pipeline->removeJob($job->reveal());

    $this->assertCount(0, $pipeline->getJobs());
  }

  // ---------------------------------------------------------------------------
  // createDuplicate()
  // ---------------------------------------------------------------------------

  /**
   * @covers ::createDuplicate
   */
  public function testCreateDuplicateAssignsNewId(): void {
    $uuid = $this->prophesize(UuidInterface::class);
    $uuid->generate()->willReturn(
      'aaaaaaaa-bbbb-cccc-dddd-000000000001',
      'aaaaaaaa-bbbb-cccc-dddd-000000000002',
    );
    $storage = new InMemoryPipelineStorage($uuid->reveal());
    $original = $storage->create([]);

    $duplicate = $original->createDuplicate();

    $this->assertNotSame($original->id(), $duplicate->id());
    $this->assertNotSame($original->uuid(), $duplicate->uuid());
  }

}
