<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_interrupt\Unit\Service;

use Drupal\flowdrop_interrupt\DTO\ExecutionStamp;
use Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface;
use Drupal\flowdrop_interrupt\Enum\InterruptType;
use Drupal\flowdrop_interrupt\Message\AbstractInterruptMessage;
use Drupal\flowdrop_interrupt\Message\InterruptMessageInterface;
use Drupal\flowdrop_interrupt\Service\InterruptMessageBus;
use Drupal\flowdrop_interrupt\Service\InterruptMessageHandlerInterface;
use Drupal\Tests\UnitTestCase;
use Prophecy\PhpUnit\ProphecyTrait;

/**
 * Tests the dispatch routing and registration mechanics of the bus.
 *
 * @coversDefaultClass \Drupal\flowdrop_interrupt\Service\InterruptMessageBus
 * @group flowdrop_interrupt
 */
class InterruptMessageBusTest extends UnitTestCase {

  use ProphecyTrait;

  /**
   * Tests that dispatch routes to the supporting handler.
   */
  public function testDispatchRoutesToSupportingHandler(): void {
    $entity = $this->prophesize(FlowDropInterruptInterface::class)->reveal();
    $bus = new InterruptMessageBus();
    $bus->addHandler(new InterruptMessageBusTestHandlerA($entity));

    $result = $bus->dispatch(
      new InterruptMessageBusTestMessageA('hello'),
      new ExecutionStamp(workflowId: 'wf', nodeId: 'n'),
    );

    $this->assertSame($entity, $result);
  }

  /**
   * Tests that dispatch of an unregistered message throws.
   */
  public function testDispatchOfUnregisteredMessageThrows(): void {
    $bus = new InterruptMessageBus();
    $this->expectException(\LogicException::class);
    $this->expectExceptionMessageMatches('/No InterruptMessageHandler registered for/');
    $bus->dispatch(
      new InterruptMessageBusTestMessageA('x'),
      new ExecutionStamp(workflowId: 'w', nodeId: 'n'),
    );
  }

  /**
   * Tests that multiple handlers route by concrete class.
   */
  public function testMultipleHandlersRouteByConcreteClass(): void {
    $entityA = $this->prophesize(FlowDropInterruptInterface::class)->reveal();
    $entityB = $this->prophesize(FlowDropInterruptInterface::class)->reveal();
    $bus = new InterruptMessageBus();
    $bus->addHandler(new InterruptMessageBusTestHandlerA($entityA));
    $bus->addHandler(new InterruptMessageBusTestHandlerB($entityB));

    $stamp = new ExecutionStamp(workflowId: 'w', nodeId: 'n');
    $this->assertSame($entityA, $bus->dispatch(new InterruptMessageBusTestMessageA('a'), $stamp));
    $this->assertSame($entityB, $bus->dispatch(new InterruptMessageBusTestMessageB('b'), $stamp));
  }

}

/**
 * Test fixture message type A.
 *
 * @internal
 */
final class InterruptMessageBusTestMessageA extends AbstractInterruptMessage {

  public function __construct(string $message) {
    parent::__construct(InterruptType::SchemaForm, $message);
  }

}

/**
 * Test fixture message type B.
 *
 * @internal
 */
final class InterruptMessageBusTestMessageB extends AbstractInterruptMessage {

  public function __construct(string $message) {
    parent::__construct(InterruptType::Cancel, $message);
  }

}

/**
 * Test fixture handler for message A.
 *
 * @internal
 */
final class InterruptMessageBusTestHandlerA implements InterruptMessageHandlerInterface {

  public function __construct(private readonly FlowDropInterruptInterface $entity) {}

  /**
   * {@inheritdoc}
   */
  public static function supports(): string {
    return InterruptMessageBusTestMessageA::class;
  }

  /**
   * {@inheritdoc}
   */
  public function handle(InterruptMessageInterface $message, ExecutionStamp $stamp): FlowDropInterruptInterface {
    return $this->entity;
  }

}

/**
 * Test fixture handler for message B.
 *
 * @internal
 */
final class InterruptMessageBusTestHandlerB implements InterruptMessageHandlerInterface {

  public function __construct(private readonly FlowDropInterruptInterface $entity) {}

  /**
   * {@inheritdoc}
   */
  public static function supports(): string {
    return InterruptMessageBusTestMessageB::class;
  }

  /**
   * {@inheritdoc}
   */
  public function handle(InterruptMessageInterface $message, ExecutionStamp $stamp): FlowDropInterruptInterface {
    return $this->entity;
  }

}
