<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_interrupt\Unit\DependencyInjection\Compiler;

use Drupal\flowdrop_interrupt\DependencyInjection\Compiler\InterruptMessageHandlerPass;
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\InterruptMessageHandlerInterface;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

/**
 * @coversDefaultClass \Drupal\flowdrop_interrupt\DependencyInjection\Compiler\InterruptMessageHandlerPass
 * @group flowdrop_interrupt
 */
class InterruptMessageHandlerPassTest extends UnitTestCase {

  /**
   * Tests that the pass accepts a valid handler.
   */
  public function testAcceptsValidHandler(): void {
    $container = new ContainerBuilder();
    $container->setDefinition('test.handler_a', $this->handlerDefinition(PassTestHandlerA::class));

    (new InterruptMessageHandlerPass())->process($container);
    $this->assertTrue(TRUE, 'pass succeeded without throwing');
  }

  /**
   * Tests that the pass rejects handlers not implementing the interface.
   */
  public function testRejectsHandlerNotImplementingInterface(): void {
    $container = new ContainerBuilder();
    $container->setDefinition(
      'test.bad_handler',
      $this->handlerDefinition(\stdClass::class),
    );

    $this->expectException(\LogicException::class);
    $this->expectExceptionMessageMatches('/does not implement/');
    (new InterruptMessageHandlerPass())->process($container);
  }

  /**
   * Tests that the pass rejects duplicate message classes.
   */
  public function testRejectsDuplicateMessageClass(): void {
    $container = new ContainerBuilder();
    $container->setDefinition('test.handler_a', $this->handlerDefinition(PassTestHandlerA::class));
    $container->setDefinition('test.handler_a_duplicate', $this->handlerDefinition(PassTestHandlerADuplicate::class));

    $this->expectException(\LogicException::class);
    $this->expectExceptionMessageMatches('/Duplicate .* handler/');
    (new InterruptMessageHandlerPass())->process($container);
  }

  /**
   * Tests that the pass rejects tagged services without a class.
   */
  public function testRejectsTaggedServiceWithoutClass(): void {
    $container = new ContainerBuilder();
    $definition = (new Definition())->addTag(InterruptMessageHandlerPass::TAG);
    $container->setDefinition('test.handler_no_class', $definition);

    $this->expectException(\LogicException::class);
    $this->expectExceptionMessageMatches('/has no class/');
    (new InterruptMessageHandlerPass())->process($container);
  }

  /**
   * Builds a tagged handler definition for the given class.
   */
  private function handlerDefinition(string $class): Definition {
    return (new Definition($class))->addTag(InterruptMessageHandlerPass::TAG);
  }

}

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

  public function __construct() {
    parent::__construct(InterruptType::SchemaForm, 'a');
  }

}

/**
 * Test fixture handler A.
 *
 * @internal
 */
final class PassTestHandlerA implements InterruptMessageHandlerInterface {

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

  /**
   * {@inheritdoc}
   */
  public function handle(InterruptMessageInterface $message, ExecutionStamp $stamp): FlowDropInterruptInterface {
    throw new \LogicException('test-only');
  }

}

/**
 * Test fixture handler duplicating PassTestHandlerA's supports() value.
 *
 * @internal
 */
final class PassTestHandlerADuplicate implements InterruptMessageHandlerInterface {

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

  /**
   * {@inheritdoc}
   */
  public function handle(InterruptMessageInterface $message, ExecutionStamp $stamp): FlowDropInterruptInterface {
    throw new \LogicException('test-only');
  }

}
