<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_ai_context\Kernel\Plugin\FlowDropNodeProcessor;

use Drupal\ai_context\Entity\AiContextItem;
use Drupal\flowdrop\DTO\ExecutionContextDTO;
use Drupal\flowdrop\DTO\ParameterBag;
use Drupal\flowdrop_ai_context\Plugin\FlowDropNodeProcessor\AiContextInjector;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\Entity\User;

/**
 * @coversDefaultClass \Drupal\flowdrop_ai_context\Plugin\FlowDropNodeProcessor\AiContextInjector
 * @group flowdrop_ai_context
 * @runTestsInSeparateProcesses
 */
final class AiContextInjectorTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'user',
    'field',
    'datetime',
    'text',
    'filter',
    'options',
    'node',
    'taxonomy',
    'views',
    'content_moderation',
    'workflows',
    'key',
    'modeler_api',
    'ai',
    'ai_agents',
    'scheduler',
    'scheduler_content_moderation_integration',
    'ai_context',
    'flowdrop',
    'flowdrop_ai_context',
  ];

  /**
   * Owner account for created context entities.
   */
  private User $owner;

  /**
   * The processor under test.
   */
  private AiContextInjector $processor;

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

    $this->installEntitySchema('user');
    $this->installEntitySchema('taxonomy_vocabulary');
    $this->installEntitySchema('taxonomy_term');
    $this->installEntitySchema('ai_context_item');
    $this->installEntitySchema('ai_context_usage');
    $this->installSchema('system', ['sequences']);
    $this->installConfig(['filter', 'ai_context']);

    // User 1 (created first) bypasses access checks.
    $admin = User::create(['name' => 'admin', 'status' => 1]);
    $admin->save();
    $this->owner = $admin;
    $this->container->get('current_user')->setAccount($admin);

    $this->processor = AiContextInjector::create(
      $this->container,
      [],
      'ai_context_injector',
      ['id' => 'ai_context_injector'],
    );
  }

  /**
   * @covers ::validateParams
   */
  public function testValidateParamsRejectsMissingContextId(): void {
    $this->assertFalse($this->processor->validateParams([])->isValid());
    $this->assertFalse($this->processor->validateParams(['context_id' => ''])->isValid());
  }

  /**
   * @covers ::validateParams
   */
  public function testValidateParamsAcceptsContextId(): void {
    $this->assertTrue($this->processor->validateParams(['context_id' => '5'])->isValid());
  }

  /**
   * @covers ::process
   */
  public function testProcessReturnsFailureWhenContextIdNotNumeric(): void {
    $output = $this->processor->process(new ParameterBag(['context_id' => 'abc']));

    $this->assertFalse($output['success']);
    $this->assertSame('abc', $output['context_id']);
    $this->assertStringContainsString('Invalid', $output['error_message']);
    $this->assertSame('', $output['rendered_text']);
  }

  /**
   * @covers ::process
   */
  public function testProcessReturnsFailureWhenEntityNotFound(): void {
    $output = $this->processor->process(new ParameterBag(['context_id' => '99999']));

    $this->assertFalse($output['success']);
    $this->assertStringContainsString('not found', $output['error_message']);
  }

  /**
   * @covers ::process
   */
  public function testProcessReturnsFailureForUnpublishedItem(): void {
    $item = $this->createContextItem('Hidden', 'private content', ['status' => 0]);

    $output = $this->processor->process(new ParameterBag(['context_id' => (string) $item->id()]));

    $this->assertFalse($output['success']);
    $this->assertStringContainsString('not published', $output['error_message']);
  }

  /**
   * @covers ::process
   */
  public function testProcessReturnsSuccessfulRenderForPublishedItem(): void {
    $item = $this->createContextItem('Brand Voice', 'Use friendly tone.');

    $output = $this->processor->process(new ParameterBag(['context_id' => (string) $item->id()]));

    $this->assertTrue($output['success']);
    $this->assertSame('', $output['error_message']);
    $this->assertSame((string) $item->id(), $output['context_id']);
    $this->assertSame('Brand Voice', $output['label']);
    $this->assertStringContainsString('Use friendly tone.', $output['content']);
    $this->assertStringContainsString('- id: ' . $item->id(), $output['rendered_text']);
    $this->assertStringContainsString('label: ', $output['rendered_text']);
    $this->assertStringContainsString('guidance:', $output['rendered_text']);
    $this->assertNotSame('', $output['purpose']);
  }

  /**
   * @covers ::process
   */
  public function testProcessRecordsUsageWhenTrackingEnabled(): void {
    $this->config('ai_context.settings')->set('usage_tracking_enabled', TRUE)->save();
    $item = $this->createContextItem('Brand Voice', 'Use friendly tone.');

    $this->processor->setExecutionContext(
      new ExecutionContextDTO([], 'wf_test', 'pipe_1', 'exec_1', 'node_1'),
    );

    $output = $this->processor->process(new ParameterBag(['context_id' => (string) $item->id()]));
    $this->assertTrue($output['success']);

    $usage = $this->container->get('entity_type.manager')
      ->getStorage('ai_context_usage')
      ->loadMultiple();
    $this->assertNotEmpty($usage);
  }

  /**
   * Creates one test context item.
   *
   * @param string $label
   *   The label of the context item.
   * @param string $content
   *   The content (guidance) of the context item.
   * @param array<string, mixed> $overrides
   *   Entity field overrides.
   */
  private function createContextItem(string $label, string $content, array $overrides = []): AiContextItem {
    $item = AiContextItem::create(array_merge([
      'type' => 'default',
      'uid' => $this->owner->id(),
      'label' => $label,
      'description' => [['value' => 'Desc for ' . $label, 'format' => 'plain_text']],
      'purpose' => [['value' => 'Purpose for ' . $label, 'format' => 'plain_text']],
      'content' => [['value' => $content, 'format' => 'plain_text']],
      'status' => 1,
      'is_global' => 0,
    ], $overrides));
    $item->save();
    return $item;
  }

}
