<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_node_processor\Kernel;

use Drupal\KernelTests\KernelTestBase;
use Drupal\flowdrop\DTO\ParameterBag;
use Drupal\flowdrop_node_processor\Plugin\FlowDropNodeProcessor\PromptTemplate;

/**
 * Tests the Prompt Template processor's Twig rendering and dynamic ports.
 *
 * Uses a Kernel test (not a plain unit test) because the processor renders
 * through the real `twig` service — the behaviour under test is the merge of
 * the `variables` bag and named dynamic input ports into the Twig context.
 *
 * @coversDefaultClass \Drupal\flowdrop_node_processor\Plugin\FlowDropNodeProcessor\PromptTemplate
 * @group flowdrop_node_processor
 */
class PromptTemplateTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = ['system', 'flowdrop', 'flowdrop_node_processor'];

  /**
   * Builds a PromptTemplate processor backed by the real Twig environment.
   */
  private function createProcessor(): PromptTemplate {
    return PromptTemplate::create(
      $this->container,
      [],
      'prompt_template',
      ['id' => 'prompt_template'],
    );
  }

  /**
   * A declared port value is exposed as a top-level Twig variable.
   *
   * @covers ::process
   */
  public function testNamedPortRendersAsTopLevelVariable(): void {
    $processor = $this->createProcessor();

    $result = $processor->process(new ParameterBag([
      'template' => 'Hello {{ name }}!',
      'dynamicInputs' => [
        ['name' => 'name', 'label' => 'Name', 'dataType' => 'string'],
      ],
      'name' => 'Alice',
    ]));

    $this->assertSame('Hello Alice!', $result['prompt']);
  }

  /**
   * A named port wins over a same-key entry in the variables bag.
   *
   * @covers ::process
   */
  public function testNamedPortOverridesBagEntry(): void {
    $processor = $this->createProcessor();

    $result = $processor->process(new ParameterBag([
      'template' => '{{ name }}',
      'variables' => ['name' => 'FromBag'],
      'dynamicInputs' => [
        ['name' => 'name', 'label' => 'Name', 'dataType' => 'string'],
      ],
      'name' => 'FromPort',
    ]));

    $this->assertSame('FromPort', $result['prompt']);
  }

  /**
   * An unconnected declared port must not clobber a bag key of the same name.
   *
   * @covers ::process
   */
  public function testUnconnectedPortDoesNotClobberBag(): void {
    $processor = $this->createProcessor();

    $result = $processor->process(new ParameterBag([
      'template' => '{{ name }}',
      'variables' => ['name' => 'FromBag'],
      'dynamicInputs' => [
        // Declared but not connected: no top-level "name" value supplied.
        ['name' => 'name', 'label' => 'Name', 'dataType' => 'string'],
      ],
    ]));

    $this->assertSame('FromBag', $result['prompt']);
  }

  /**
   * The whole variables bag stays reachable as {{ variables.x }} (BC).
   *
   * @covers ::process
   */
  public function testVariablesBagRemainsAddressable(): void {
    $processor = $this->createProcessor();

    $result = $processor->process(new ParameterBag([
      'template' => '{{ variables.city }}',
      'variables' => ['city' => 'Berlin'],
    ]));

    $this->assertSame('Berlin', $result['prompt']);
  }

  /**
   * The bag-only path (no dynamic ports) behaves exactly as before.
   *
   * @covers ::process
   */
  public function testBagOnlyPathUnchanged(): void {
    $processor = $this->createProcessor();

    $result = $processor->process(new ParameterBag([
      'template' => '{{ greeting }}, {{ subject }}',
      'variables' => ['greeting' => 'Hi', 'subject' => 'there'],
    ]));

    $this->assertSame('Hi, there', $result['prompt']);
  }

  /**
   * A reserved dynamic port name is rejected (delegated to DynamicPortTrait).
   *
   * @covers ::process
   */
  public function testReservedPortNameRejected(): void {
    $processor = $this->createProcessor();

    $this->expectException(\RuntimeException::class);
    $this->expectExceptionMessage('reserved');
    $processor->process(new ParameterBag([
      'template' => 'x',
      'dynamicInputs' => [
        ['name' => 'trigger', 'label' => 'Trigger', 'dataType' => 'string'],
      ],
    ]));
  }

  /**
   * The parameter schema exposes template, variables and dynamicInputs.
   *
   * @covers ::getParameterSchema
   */
  public function testParameterSchemaExposesDynamicInputs(): void {
    $processor = $this->createProcessor();
    $schema = $processor->getParameterSchema();

    $this->assertArrayHasKey('template', $schema['properties']);
    $this->assertArrayHasKey('variables', $schema['properties']);
    $this->assertArrayHasKey('dynamicInputs', $schema['properties']);
    // Prompt template has fixed outputs — no dynamic output ports.
    $this->assertArrayNotHasKey('dynamicOutputs', $schema['properties']);
  }

}
