<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_interrupt\Unit\EventSubscriber;

use Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface;
use Drupal\flowdrop_interrupt\Event\InterruptResolvedEvent;
use Drupal\flowdrop_interrupt\EventSubscriber\LinkedInterruptResolvedSubscriber;
use Drupal\flowdrop_interrupt\Service\InterruptManagerInterface;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\LoggerInterface;

/**
 * Tests the generic linked-interrupt cascade subscriber.
 *
 * @coversDefaultClass \Drupal\flowdrop_interrupt\EventSubscriber\LinkedInterruptResolvedSubscriber
 * @group flowdrop_interrupt
 */
class LinkedInterruptResolvedSubscriberTest extends UnitTestCase {

  use ProphecyTrait;

  /**
   * Followers are resolved with the originating interrupt's payload.
   */
  public function testFollowersResolvedWithSamePayload(): void {
    $manager = $this->prophesize(InterruptManagerInterface::class);
    $logger = $this->prophesize(LoggerInterface::class);

    $resolved = $this->prophesize(FlowDropInterruptInterface::class);
    $resolved->uuid()->willReturn('uuid-resolved');

    $follower1 = $this->prophesize(FlowDropInterruptInterface::class);
    $follower1->uuid()->willReturn('uuid-follower-1');
    $follower2 = $this->prophesize(FlowDropInterruptInterface::class);
    $follower2->uuid()->willReturn('uuid-follower-2');

    $manager->getPendingInterruptsLinkedTo('uuid-resolved')
      ->willReturn([$follower1->reveal(), $follower2->reveal()]);
    $manager->resolveInterrupt('uuid-follower-1', ['out' => 1], 42)
      ->shouldBeCalled();
    $manager->resolveInterrupt('uuid-follower-2', ['out' => 1], 42)
      ->shouldBeCalled();

    $subscriber = new LinkedInterruptResolvedSubscriber(
      $manager->reveal(),
      $logger->reveal(),
    );

    $subscriber->onInterruptResolved(new InterruptResolvedEvent(
      interrupt: $resolved->reveal(),
      responseData: ['out' => 1],
      resolvedBy: 42,
    ));
  }

  /**
   * No followers → no resolveInterrupt calls.
   */
  public function testNoFollowersIsNoop(): void {
    $manager = $this->prophesize(InterruptManagerInterface::class);
    $logger = $this->prophesize(LoggerInterface::class);

    $resolved = $this->prophesize(FlowDropInterruptInterface::class);
    $resolved->uuid()->willReturn('uuid-resolved');

    $manager->getPendingInterruptsLinkedTo('uuid-resolved')->willReturn([]);
    $manager->resolveInterrupt(Argument::cetera())->shouldNotBeCalled();

    $subscriber = new LinkedInterruptResolvedSubscriber(
      $manager->reveal(),
      $logger->reveal(),
    );
    $subscriber->onInterruptResolved(new InterruptResolvedEvent(
      interrupt: $resolved->reveal(),
      responseData: NULL,
      resolvedBy: NULL,
    ));
  }

  /**
   * Self-link is skipped (no infinite loops, no nonsense resolutions).
   */
  public function testSelfLinkIsSkipped(): void {
    $manager = $this->prophesize(InterruptManagerInterface::class);
    $logger = $this->prophesize(LoggerInterface::class);

    $resolved = $this->prophesize(FlowDropInterruptInterface::class);
    $resolved->uuid()->willReturn('uuid-resolved');

    // Manager pathologically returns the resolved interrupt itself —
    // the subscriber must not try to re-resolve it.
    $manager->getPendingInterruptsLinkedTo('uuid-resolved')
      ->willReturn([$resolved->reveal()]);
    $manager->resolveInterrupt(Argument::cetera())->shouldNotBeCalled();

    $subscriber = new LinkedInterruptResolvedSubscriber(
      $manager->reveal(),
      $logger->reveal(),
    );
    $subscriber->onInterruptResolved(new InterruptResolvedEvent(
      interrupt: $resolved->reveal(),
      responseData: NULL,
      resolvedBy: NULL,
    ));
  }

  /**
   * A failing follower resolution doesn't block the next.
   */
  public function testOneFollowerFailureDoesNotBlockOthers(): void {
    $manager = $this->prophesize(InterruptManagerInterface::class);
    $logger = $this->prophesize(LoggerInterface::class);

    $resolved = $this->prophesize(FlowDropInterruptInterface::class);
    $resolved->uuid()->willReturn('uuid-resolved');

    $follower1 = $this->prophesize(FlowDropInterruptInterface::class);
    $follower1->uuid()->willReturn('uuid-follower-1');
    $follower2 = $this->prophesize(FlowDropInterruptInterface::class);
    $follower2->uuid()->willReturn('uuid-follower-2');

    $manager->getPendingInterruptsLinkedTo('uuid-resolved')
      ->willReturn([$follower1->reveal(), $follower2->reveal()]);
    $manager->resolveInterrupt('uuid-follower-1', NULL, NULL)
      ->willThrow(new \RuntimeException('boom'));
    $manager->resolveInterrupt('uuid-follower-2', NULL, NULL)
      ->shouldBeCalled();
    $logger->error(Argument::cetera())->shouldBeCalled();

    $subscriber = new LinkedInterruptResolvedSubscriber(
      $manager->reveal(),
      $logger->reveal(),
    );
    $subscriber->onInterruptResolved(new InterruptResolvedEvent(
      interrupt: $resolved->reveal(),
      responseData: NULL,
      resolvedBy: NULL,
    ));
  }

}
