<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_interrupt\Unit\Service;

use Drupal\flowdrop_interrupt\Service\InterruptFactoryInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface;
use Drupal\flowdrop_interrupt\Enum\InterruptDirection;
use Drupal\flowdrop_interrupt\Enum\InterruptStatus;
use Drupal\flowdrop_interrupt\Service\InterruptManager;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Tests Phase 3 inward-signal lookup methods on InterruptManager.
 *
 * @coversDefaultClass \Drupal\flowdrop_interrupt\Service\InterruptManager
 * @group flowdrop_interrupt
 */
class InwardSignalLookupTest extends UnitTestCase {

  use ProphecyTrait;

  /**
   * Mocked entity type manager.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy
   */
  private ObjectProphecy $entityTypeManager;

  /**
   * Mocked entity storage.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy
   */
  private ObjectProphecy $storage;

  /**
   * Interrupt manager under test.
   */
  private InterruptManager $manager;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
    $this->storage = $this->prophesize(EntityStorageInterface::class);
    $this->entityTypeManager->getStorage('flowdrop_interrupt')->willReturn($this->storage->reveal());

    $factory = $this->prophesize(InterruptFactoryInterface::class)->reveal();
    $this->manager = new InterruptManager(
      $this->entityTypeManager->reveal(),
      $this->prophesize(AccountProxyInterface::class)->reveal(),
      $this->prophesize(LoggerInterface::class)->reveal(),
      $this->prophesize(EventDispatcherInterface::class)->reveal(),
      $this->prophesize(LockBackendInterface::class)->reveal(),
      $factory,
    );
  }

  /**
   * The findPendingInwardSignal: indexed query, returns first matching entity.
   */
  public function testFindPendingInwardSignalReturnsEntity(): void {
    $query = $this->buildQueryStub(['42']);
    $this->storage->getQuery()->willReturn($query);

    $entity = $this->prophesize(FlowDropInterruptInterface::class);
    $this->storage->load('42')->willReturn($entity->reveal());

    $result = $this->manager->findPendingInwardSignal('pipe_99');
    $this->assertSame($entity->reveal(), $result);
  }

  /**
   * The findPendingInwardSignal: no match → NULL.
   */
  public function testFindPendingInwardSignalNoMatch(): void {
    $query = $this->buildQueryStub([]);
    $this->storage->getQuery()->willReturn($query);
    $this->storage->load(Argument::any())->shouldNotBeCalled();

    $this->assertNull($this->manager->findPendingInwardSignal('pipe_99'));
  }

  /**
   * The cancelOutwardInterruptsForPipeline cancels and counts.
   *
   * Cancels each pending outward interrupt for the pipeline and returns
   * the count.
   */
  public function testCancelOutwardInterruptsCancelsAll(): void {
    $query = $this->buildOutwardCancelQueryStub(['1', '2', '3']);
    $this->storage->getQuery()->willReturn($query);

    $entities = [];
    foreach (['1', '2', '3'] as $id) {
      $e = $this->prophesize(FlowDropInterruptInterface::class);
      $e->cancel()->shouldBeCalled()->willReturn($e->reveal());
      $e->save()->shouldBeCalled();
      $entities[$id] = $e->reveal();
    }
    $this->storage->loadMultiple(['1', '2', '3'])->willReturn($entities);

    $this->assertSame(3, $this->manager->cancelOutwardInterruptsForPipeline('pipe_99'));
  }

  /**
   * The cancelOutwardInterruptsForPipeline: no matches → 0, no loads.
   */
  public function testCancelOutwardInterruptsEmptyReturnsZero(): void {
    $query = $this->buildOutwardCancelQueryStub([]);
    $this->storage->getQuery()->willReturn($query);
    $this->storage->loadMultiple(Argument::any())->shouldNotBeCalled();

    $this->assertSame(0, $this->manager->cancelOutwardInterruptsForPipeline('pipe_99'));
  }

  /**
   * Builds a query stub for findPendingInwardSignal.
   *
   * Asserts the conditions match the indexed inward-poll lookup contract.
   */
  private function buildQueryStub(array $resultIds): QueryInterface {
    $query = $this->prophesize(QueryInterface::class);
    $query->accessCheck(FALSE)->willReturn($query->reveal());
    $query->condition('target_pipeline_id', 'pipe_99')->shouldBeCalled()->willReturn($query->reveal());
    $query->condition('direction', InterruptDirection::Inward->value)->shouldBeCalled()->willReturn($query->reveal());
    $query->condition('status', InterruptStatus::Pending->value)->shouldBeCalled()->willReturn($query->reveal());
    $query->sort('created', 'ASC')->willReturn($query->reveal());
    $query->range(0, 1)->shouldBeCalled()->willReturn($query->reveal());
    $query->execute()->willReturn($resultIds);
    return $query->reveal();
  }

  /**
   * Builds a query stub for cancelOutwardInterruptsForPipeline.
   */
  private function buildOutwardCancelQueryStub(array $resultIds): QueryInterface {
    $query = $this->prophesize(QueryInterface::class);
    $query->accessCheck(FALSE)->willReturn($query->reveal());
    $query->condition('pipeline_id', 'pipe_99')->shouldBeCalled()->willReturn($query->reveal());
    $query->condition('direction', InterruptDirection::Outward->value)->shouldBeCalled()->willReturn($query->reveal());
    $query->condition('status', InterruptStatus::Pending->value)->shouldBeCalled()->willReturn($query->reveal());
    $query->execute()->willReturn($resultIds);
    return $query->reveal();
  }

}
