<?php

declare(strict_types=1);

namespace Drupal\Tests\ai_automators\Kernel;

use Drupal\ai_automators\Entity\AiAutomator;
use Drupal\ai_automators\Form\AiAutomatorFormBase;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\Entity\BaseFieldOverride;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\NodeType;

/**
 * Tests AI Automator support for base fields.
 *
 * @group ai_automators
 *
 * @see https://www.drupal.org/i/3558728
 */
final class BaseFieldAutomatorTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   *
   * @var array<string>
   */
  protected static $modules = [
    'system',
    'user',
    'field',
    'text',
    'filter',
    'file',
    'node',
    'ai',
    'ai_automators',
    'token',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('user');
    $this->installEntitySchema('node');
    $this->installConfig(['node', 'field', 'filter']);
    NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
  }

  /**
   * The automator add form.
   *
   * @return \Drupal\ai_automators\Form\AiAutomatorFormBase
   *   The form object.
   */
  private function addForm(): AiAutomatorFormBase {
    $form = \Drupal::entityTypeManager()->getFormObject('ai_automator', 'add');
    assert($form instanceof AiAutomatorFormBase);
    return $form;
  }

  /**
   * Invokes a protected method on the add form.
   *
   * @param string $method
   *   The protected method name.
   * @param array<int, mixed> $args
   *   The method arguments.
   *
   * @return mixed
   *   The method return value.
   */
  private function invokeFormMethod(string $method, array $args): mixed {
    $form = $this->addForm();
    $ref = new \ReflectionMethod($form, $method);
    $ref->setAccessible(TRUE);
    return $ref->invoke($form, ...$args);
  }

  /**
   * Base fields (e.g. node title) are offered; identifiers are excluded.
   */
  public function testGetEntityFieldsIncludesBaseFields(): void {
    // Add a configurable field too, so the field list contains both a base
    // field (title) and a configurable field (FieldConfig). This also guards
    // against calling storage-only methods that base/config fields differ on.
    FieldStorageConfig::create([
      'field_name' => 'field_blurb',
      'entity_type' => 'node',
      'type' => 'string',
    ])->save();
    FieldConfig::create([
      'field_name' => 'field_blurb',
      'entity_type' => 'node',
      'bundle' => 'article',
      'label' => 'Blurb',
    ])->save();

    $fields = $this->invokeFormMethod('getEntityFields', ['node', 'article']);

    $this->assertArrayHasKey('title', $fields, 'The title base field is offered for automation.');
    $this->assertArrayHasKey('field_blurb', $fields, 'A configurable field is still offered.');
    $this->assertArrayNotHasKey('nid', $fields, 'The id base field is not offered.');
    $this->assertArrayNotHasKey('uuid', $fields, 'The uuid base field is not offered.');
  }

  /**
   * Attaching to a plain base field creates a base field override.
   */
  public function testEnsureBaseFieldOverrideCreatesOverride(): void {
    $storage = \Drupal::entityTypeManager()->getStorage('base_field_override');
    $this->assertNull($storage->load('node.article.title'), 'No override exists yet.');

    $this->invokeFormMethod('ensureBaseFieldOverride', ['node', 'article', 'title']);

    $overrides = $storage->loadByProperties([
      'entity_type' => 'node',
      'bundle' => 'article',
      'field_name' => 'title',
    ]);
    $this->assertCount(1, $overrides, 'A base field override was created for the title base field.');
  }

  /**
   * A base-field automator depends on the base field override config.
   */
  public function testBaseFieldDependencyName(): void {
    // Once a base field has an override (which save() creates when an automator
    // is attached), the field definition is a config entity and the automator
    // depends on the base field override config object.
    $definition = \Drupal::service('entity_field.manager')
      ->getFieldDefinitions('node', 'article')['title'];
    assert($definition instanceof BaseFieldDefinition);
    BaseFieldOverride::createFromBaseFieldDefinition($definition, 'article')->save();
    \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();

    $automator = AiAutomator::create([
      'id' => 'title_automator',
      'label' => 'Title automator',
      'entity_type' => 'node',
      'bundle' => 'article',
      'field_name' => 'title',
    ]);
    $automator->calculateDependencies();
    $config_deps = $automator->getDependencies()['config'] ?? [];

    $this->assertContains('core.base_field_override.node.article.title', $config_deps);
    $this->assertNotContains('field.field.node.article.title', $config_deps);
  }

  /**
   * A configurable-field automator still depends on the field config.
   */
  public function testConfigurableFieldDependencyName(): void {
    FieldStorageConfig::create([
      'field_name' => 'field_summary',
      'entity_type' => 'node',
      'type' => 'string',
    ])->save();
    FieldConfig::create([
      'field_name' => 'field_summary',
      'entity_type' => 'node',
      'bundle' => 'article',
      'label' => 'Summary',
    ])->save();

    $automator = AiAutomator::create([
      'id' => 'summary_automator',
      'label' => 'Summary automator',
      'entity_type' => 'node',
      'bundle' => 'article',
      'field_name' => 'field_summary',
    ]);
    $automator->calculateDependencies();
    $config_deps = $automator->getDependencies()['config'] ?? [];

    $this->assertContains('field.field.node.article.field_summary', $config_deps);
    $this->assertNotContains('core.base_field_override.node.article.field_summary', $config_deps);
  }

}
