<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_pipeline\Kernel;

use Drupal\KernelTests\KernelTestBase;
use Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface;
use Drupal\flowdrop_pipeline\Service\PipelineLineageResolver;

/**
 * Covers PipelineLineageResolver: numeric-filter, existence and session scope.
 *
 * The flowdrop_session module is enabled because it provides the session_id
 * column the resolver scopes on.
 *
 * @group flowdrop_pipeline
 */
class PipelineLineageResolverTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'user',
    'field',
    'options',
    'text',
    'flowdrop',
    'flowdrop_workflow',
    'flowdrop_orchestration',
    'flowdrop_runtime',
    'flowdrop_pipeline',
    'flowdrop_job',
    'flowdrop_session',
    'flowdrop_node_type',
  ];

  /**
   * The resolver under test.
   */
  private PipelineLineageResolver $resolver;

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

    $this->installEntitySchema('user');
    $this->installEntitySchema('flowdrop_workflow');
    $this->installEntitySchema('flowdrop_workflow_snapshot');
    $this->installEntitySchema('flowdrop_pipeline');
    $this->installEntitySchema('flowdrop_job');
    $this->installEntitySchema('flowdrop_session');
    $this->installEntitySchema('flowdrop_session_message');
    $this->installEntitySchema('flowdrop_node_type');

    $this->resolver = $this->container->get(PipelineLineageResolver::class);
  }

  /**
   * Resolves the root and parent of a top-level run and its nested sub-run.
   */
  public function testResolvesRootAndParent(): void {
    $root = $this->createPipeline(1);
    $sub = $this->createPipeline(1, [
      'parent_pipeline_id' => $root->id(),
      'root_pipeline_id' => $root->id(),
    ]);

    $map = $this->resolver->resolveForSession([$root->id(), $sub->id()], 1);

    $this->assertSame((string) $root->id(), $map[(string) $root->id()]['root']);
    $this->assertNull($map[(string) $root->id()]['parent']);

    $this->assertSame((string) $root->id(), $map[(string) $sub->id()]['root']);
    $this->assertSame((string) $root->id(), $map[(string) $sub->id()]['parent']);
  }

  /**
   * A pipeline in another session is not resolved (session scope).
   */
  public function testSessionScopeExcludesOtherSession(): void {
    $other = $this->createPipeline(2);

    $map = $this->resolver->resolveForSession([$other->id()], 1);

    $this->assertArrayNotHasKey((string) $other->id(), $map);
    $this->assertSame([], $map);
  }

  /**
   * Non-numeric (external) execution ids are dropped before the query.
   */
  public function testNumericFilterDropsExternalIds(): void {
    $root = $this->createPipeline(1);

    $map = $this->resolver->resolveForSession(['ext-abc-123', '', NULL, $root->id()], 1);

    $this->assertCount(1, $map);
    $this->assertArrayHasKey((string) $root->id(), $map);
  }

  /**
   * A numeric id that is not a pipeline in this session resolves to nothing.
   */
  public function testUnknownIdAbsent(): void {
    $root = $this->createPipeline(1);
    $missing = (int) $root->id() + 99999;

    $map = $this->resolver->resolveForSession([$missing], 1);

    $this->assertSame([], $map);
  }

  /**
   * Empty input short-circuits to an empty map.
   */
  public function testEmptyInput(): void {
    $this->assertSame([], $this->resolver->resolveForSession([], 1));
  }

  /**
   * Creates and saves a pipeline bound to a session.
   *
   * @param int $session_id
   *   The session id to stamp (no real session entity is required — the
   *   resolver only filters on the integer value).
   * @param array<string, mixed> $values
   *   Extra field values.
   *
   * @return \Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface
   *   The saved pipeline.
   */
  private function createPipeline(int $session_id, array $values = []): FlowDropPipelineInterface {
    $storage = $this->container->get('entity_type.manager')->getStorage('flowdrop_pipeline');
    $pipeline = $storage->create([
      'label' => 'Test Pipeline',
      'bundle' => 'default',
      'status' => 'pending',
      'session_id' => $session_id,
    ] + $values);
    $pipeline->save();
    assert($pipeline instanceof FlowDropPipelineInterface);
    return $pipeline;
  }

}
