<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_session\Unit\Service;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorHelperInterface;
use Drupal\flowdrop_runtime\Service\Storage\PipelineStorageRegistryInterface;
use Drupal\flowdrop_session\Entity\FlowDropSessionInterface;
use Drupal\flowdrop_session\Entity\FlowDropSessionMessageInterface;
use Drupal\flowdrop_session\Service\SessionExecutionService;
use Drupal\flowdrop_session\Service\SessionService;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
 * Pins the StateGraph option-building rules for session runs.
 *
 * Sessions resume HITL interrupts in a later request, so they must default
 * to the persistent (entity) checkpointer — memory-checkpointer state does
 * not survive the original request and the resumed graph would restart
 * with an empty state channel. Only an explicit session-level setting may
 * override that default; the workflow-declared nested value is deliberately
 * ignored because the StateGraph plugin form persists its `memory` default
 * into every workflow's metadata.
 *
 * @coversDefaultClass \Drupal\flowdrop_session\Service\SessionExecutionService
 * @group flowdrop_session
 */
class SessionExecutionServiceOptionsTest extends UnitTestCase {

  use ProphecyTrait;

  /**
   * The service under test.
   */
  private SessionExecutionService $service;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $loggerChannel = $this->prophesize(LoggerChannelInterface::class);
    $loggerFactory = $this->prophesize(LoggerChannelFactoryInterface::class);
    $loggerFactory->get('flowdrop_session')->willReturn($loggerChannel->reveal());

    $orchestratorHelper = $this->prophesize(OrchestratorHelperInterface::class);
    $orchestratorHelper->isStateGraph(Argument::any())->willReturn(TRUE);

    $this->service = new SessionExecutionService(
      $this->prophesize(EntityTypeManagerInterface::class)->reveal(),
      $loggerFactory->reveal(),
      $orchestratorHelper->reveal(),
      $this->prophesize(SessionService::class)->reveal(),
      $this->prophesize(PipelineStorageRegistryInterface::class)->reveal(),
      $this->prophesize(TimeInterface::class)->reveal(),
      $this->prophesize(EventDispatcherInterface::class)->reveal(),
    );
  }

  /**
   * Invokes the private buildOrchestrationOptions helper.
   *
   * @param array<string, mixed> $orchestratorSettings
   *   The orchestrator settings.
   *
   * @return array<string, mixed>
   *   The built options.
   */
  private function buildOptions(array $orchestratorSettings): array {
    $session = $this->prophesize(FlowDropSessionInterface::class);
    $session->id()->willReturn('42');
    $message = $this->prophesize(FlowDropSessionMessageInterface::class);
    $message->id()->willReturn('99');
    $message->getSequenceNumber()->willReturn(3);

    $ref = new \ReflectionMethod(SessionExecutionService::class, 'buildOrchestrationOptions');
    return $ref->invoke(
      $this->service,
      $orchestratorSettings,
      $session->reveal(),
      $message->reveal(),
      [],
    );
  }

  /**
   * Without settings, sessions default to the persistent checkpointer.
   */
  public function testDefaultsToEntityCheckpointer(): void {
    $options = $this->buildOptions([]);

    $this->assertSame('entity', $options['stategraph']['checkpointer_type']);
    $this->assertSame('playground_session_42', $options['stategraph']['thread_id']);
    $this->assertTrue($options['stategraph']['create_checkpoints']);
    $this->assertSame(100, $options['stategraph']['max_iterations']);
  }

  /**
   * An explicit session-level setting overrides the entity default.
   */
  public function testSessionLevelCheckpointerOverrideWins(): void {
    $options = $this->buildOptions(['checkpointer_type' => 'memory']);

    $this->assertSame('memory', $options['stategraph']['checkpointer_type']);
  }

  /**
   * The workflow-declared nested value cannot downgrade a session run.
   *
   * `stategraph.checkpointer_type = memory` is the StateGraph plugin
   * form's persisted default, not author intent — honoring it would
   * silently reintroduce the lost-state-on-resume bug for every workflow
   * saved through the form.
   */
  public function testNestedWorkflowCheckpointerIsIgnored(): void {
    $options = $this->buildOptions([
      'stategraph' => ['checkpointer_type' => 'memory'],
    ]);

    $this->assertSame('entity', $options['stategraph']['checkpointer_type']);
  }

  /**
   * The workflow's nested iteration budget is honored over the default.
   */
  public function testNestedMaxIterationsWins(): void {
    $options = $this->buildOptions([
      'stategraph' => ['max_iterations' => 7],
      'max_iterations' => 50,
    ]);

    $this->assertSame(7, $options['stategraph']['max_iterations']);
  }

  /**
   * A flat session-level iteration budget still applies without a nested one.
   */
  public function testFlatMaxIterationsFallback(): void {
    $options = $this->buildOptions(['max_iterations' => 50]);

    $this->assertSame(50, $options['stategraph']['max_iterations']);
  }

  /**
   * A malformed nested settings value is ignored, not fatal.
   */
  public function testNonArrayNestedSettingsAreIgnored(): void {
    $options = $this->buildOptions(['stategraph' => 'oops']);

    $this->assertSame('entity', $options['stategraph']['checkpointer_type']);
    $this->assertSame(100, $options['stategraph']['max_iterations']);
  }

}
