<?php

namespace Drupal\Tests\eb\Kernel\Operation;

use Drupal\eb\PluginInterfaces\PreviewableOperationInterface;
use Drupal\eb\Service\OperationBuilder;
use Drupal\eb\Service\OperationProcessor;
use Drupal\eb\Service\ValidationManager;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\eb\Kernel\EbKernelTestBase;
use Drupal\Tests\eb\Traits\BundleCreationTrait;
use Drupal\Tests\eb\Traits\FieldCreationTrait;
use Drupal\Tests\eb\Traits\OperationTestTrait;
use Drupal\Tests\eb\Traits\ValidationAssertionsTrait;

/**
 * Kernel tests for field operations (create, update, delete, hide, reorder).
 *
 * @group eb
 */
class FieldOperationsKernelTest extends EbKernelTestBase {

  use BundleCreationTrait;
  use FieldCreationTrait;
  use OperationTestTrait;
  use ValidationAssertionsTrait;

  /**
   * The operation builder service.
   */
  protected OperationBuilder $operationBuilder;

  /**
   * The operation processor service.
   */
  protected OperationProcessor $operationProcessor;

  /**
   * The validation manager service.
   */
  protected ValidationManager $validationManager;

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

    $this->operationBuilder = $this->container->get('eb.operation_builder');
    $this->operationProcessor = $this->container->get('eb.operation_processor');
    $this->validationManager = $this->container->get('eb.validation_manager');

    // Create a base content type for testing.
    $this->createTestNodeType('article', 'Article');
  }

  /**
   * Tests creating a text field.
   */
  public function testCreateTextField(): void {
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_subtitle',
      'text',
      'Subtitle'
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);

    $validationResult = $operation->validate();
    $this->assertValidationResultPasses($validationResult);

    $result = $this->operationProcessor->executeOperation($operation);
    $this->assertExecutionResultSuccess($result);

    // Verify field was created.
    $this->assertFieldStorageExists('node', 'field_subtitle');
    $this->assertFieldConfigExists('node', 'article', 'field_subtitle');

    $fieldConfig = FieldConfig::loadByName('node', 'article', 'field_subtitle');
    $this->assertEquals('Subtitle', $fieldConfig->getLabel());
  }

  /**
   * Tests creating a long text field.
   */
  public function testCreateLongTextField(): void {
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_body',
      'text_long',
      'Body',
      ['required' => TRUE]
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $result = $this->operationProcessor->executeOperation($operation);

    $this->assertExecutionResultSuccess($result);
    $this->assertFieldConfigExists('node', 'article', 'field_body');

    $fieldConfig = FieldConfig::loadByName('node', 'article', 'field_body');
    $this->assertTrue($fieldConfig->isRequired());
  }

  /**
   * Tests creating an integer field.
   */
  public function testCreateIntegerField(): void {
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_count',
      'integer',
      'Count'
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $result = $this->operationProcessor->executeOperation($operation);

    $this->assertExecutionResultSuccess($result);

    $fieldStorage = FieldStorageConfig::loadByName('node', 'field_count');
    $this->assertEquals('integer', $fieldStorage->getType());
  }

  /**
   * Tests creating a boolean field.
   */
  public function testCreateBooleanField(): void {
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_featured',
      'boolean',
      'Featured'
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $result = $this->operationProcessor->executeOperation($operation);

    $this->assertExecutionResultSuccess($result);
    $this->assertFieldConfigExists('node', 'article', 'field_featured');
  }

  /**
   * Tests creating an entity reference field.
   */
  public function testCreateEntityReferenceField(): void {
    // Create target vocabulary.
    $this->createTestVocabulary('tags', 'Tags');

    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_tags',
      'entity_reference',
      'Tags',
      [
        'cardinality' => -1,
        'field_storage_settings' => [
          'target_type' => 'taxonomy_term',
        ],
      ]
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $result = $this->operationProcessor->executeOperation($operation);

    $this->assertExecutionResultSuccess($result);

    $fieldStorage = FieldStorageConfig::loadByName('node', 'field_tags');
    $this->assertEquals(-1, $fieldStorage->getCardinality());
    $this->assertEquals('taxonomy_term', $fieldStorage->getSetting('target_type'));
  }

  /**
   * Tests creating a list (select) field.
   */
  public function testCreateListField(): void {
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_category',
      'list_string',
      'Category',
      [
        'field_storage_settings' => [
          'allowed_values' => [
            'news' => 'News',
            'blog' => 'Blog',
            'article' => 'Article',
          ],
        ],
      ]
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $result = $this->operationProcessor->executeOperation($operation);

    $this->assertExecutionResultSuccess($result);

    $fieldStorage = FieldStorageConfig::loadByName('node', 'field_category');
    $allowedValues = $fieldStorage->getSetting('allowed_values');
    $this->assertArrayHasKey('news', $allowedValues);
    $this->assertArrayHasKey('blog', $allowedValues);
  }

  /**
   * Tests creating a datetime field.
   */
  public function testCreateDatetimeField(): void {
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_published',
      'datetime',
      'Published Date',
      [
        'settings' => [
          'datetime_type' => 'date',
        ],
      ]
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $result = $this->operationProcessor->executeOperation($operation);

    $this->assertExecutionResultSuccess($result);
    $this->assertFieldConfigExists('node', 'article', 'field_published');
  }

  /**
   * Tests creating a link field.
   */
  public function testCreateLinkField(): void {
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_website',
      'link',
      'Website'
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $result = $this->operationProcessor->executeOperation($operation);

    $this->assertExecutionResultSuccess($result);
    $this->assertFieldConfigExists('node', 'article', 'field_website');
  }

  /**
   * Tests create field validation fails for non-existent bundle.
   */
  public function testCreateFieldFailsForNonExistentBundle(): void {
    $data = $this->createFieldOperationData(
      'node',
      'nonexistent_bundle',
      'field_test',
      'text',
      'Test'
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);

    // Use validationManager to get full validation including validator plugins.
    // Bundle existence is checked by DependencyValidator, not the operation.
    $validationResult = $this->validationManager->validateOperation($operation);

    $this->assertValidationResultFails($validationResult);
  }

  /**
   * Tests create field validation fails for invalid field type.
   */
  public function testCreateFieldFailsForInvalidFieldType(): void {
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_test',
      'nonexistent_field_type',
      'Test'
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $validationResult = $operation->validate();

    $this->assertValidationResultFails($validationResult);
  }

  /**
   * Tests create field validation fails when field already exists.
   */
  public function testCreateFieldFailsWhenFieldExists(): void {
    // Create field first.
    $this->createTextField('node', 'article', 'field_existing', 'Existing Field');

    // Try to create same field.
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_existing',
      'text',
      'Duplicate Field'
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $validationResult = $operation->validate();

    $this->assertValidationResultFails($validationResult);
  }

  /**
   * Tests updating a field configuration.
   */
  public function testUpdateFieldConfiguration(): void {
    // Create field first.
    $this->createTextField('node', 'article', 'field_update_test', 'Original Label');

    $data = $this->updateFieldOperationData(
      'node',
      'article',
      'field_update_test',
      [
        'label' => 'Updated Label',
        'required' => TRUE,
        'description' => 'Updated description.',
      ]
    );

    $operation = $this->operationBuilder->buildOperation('update_field', $data);

    $validationResult = $operation->validate();
    $this->assertValidationResultPasses($validationResult);

    $result = $this->operationProcessor->executeOperation($operation);
    $this->assertExecutionResultSuccess($result);

    $fieldConfig = FieldConfig::loadByName('node', 'article', 'field_update_test');
    $this->assertEquals('Updated Label', $fieldConfig->getLabel());
    $this->assertTrue($fieldConfig->isRequired());
    $this->assertEquals('Updated description.', $fieldConfig->getDescription());
  }

  /**
   * Tests update field validation fails for non-existent field.
   */
  public function testUpdateFieldFailsForNonExistentField(): void {
    $data = $this->updateFieldOperationData(
      'node',
      'article',
      'field_nonexistent',
      ['label' => 'Should Fail']
    );

    $operation = $this->operationBuilder->buildOperation('update_field', $data);
    $validationResult = $operation->validate();

    $this->assertValidationResultFails($validationResult);
  }

  /**
   * Tests deleting a field.
   */
  public function testDeleteField(): void {
    // Create field first.
    $this->createTextField('node', 'article', 'field_delete_test', 'Delete Me');
    $this->assertFieldConfigExists('node', 'article', 'field_delete_test');

    $data = [
      'operation' => 'delete_field',
      'entity_type' => 'node',
      'bundle' => 'article',
      'field_name' => 'field_delete_test',
    ];

    $operation = $this->operationBuilder->buildOperation('delete_field', $data);

    $validationResult = $operation->validate();
    $this->assertValidationResultPasses($validationResult);

    $result = $this->operationProcessor->executeOperation($operation);
    $this->assertExecutionResultSuccess($result);

    $this->assertFieldConfigNotExists('node', 'article', 'field_delete_test');
  }

  /**
   * Tests field creation rollback data.
   */
  public function testFieldCreationRollbackData(): void {
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_rollback_test',
      'text',
      'Rollback Test'
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $result = $this->operationProcessor->executeOperation($operation);

    $this->assertExecutionResultSuccess($result);
    $this->assertExecutionHasRollbackData($result);

    $rollbackData = $result->getRollbackData();
    // Rollback data stores config entity IDs, not raw field data.
    $this->assertArrayHasKey('field_config_id', $rollbackData);
    $this->assertArrayHasKey('storage_created', $rollbackData);
    $this->assertArrayHasKey('storage_config_id', $rollbackData);
    // Verify the field_config_id matches expected format.
    $this->assertEquals('node.article.field_rollback_test', $rollbackData['field_config_id']);
  }

  /**
   * Tests creating a field with cardinality.
   */
  public function testCreateFieldWithCardinality(): void {
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_multiple',
      'text',
      'Multiple Values',
      ['cardinality' => 5]
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $result = $this->operationProcessor->executeOperation($operation);

    $this->assertExecutionResultSuccess($result);

    $fieldStorage = FieldStorageConfig::loadByName('node', 'field_multiple');
    $this->assertEquals(5, $fieldStorage->getCardinality());
  }

  /**
   * Tests creating multiple fields on same bundle.
   */
  public function testCreateMultipleFieldsOnBundle(): void {
    $fields = [
      ['name' => 'field_multi_1', 'type' => 'text', 'label' => 'Field 1'],
      ['name' => 'field_multi_2', 'type' => 'integer', 'label' => 'Field 2'],
      ['name' => 'field_multi_3', 'type' => 'boolean', 'label' => 'Field 3'],
    ];

    foreach ($fields as $field) {
      $data = $this->createFieldOperationData(
        'node',
        'article',
        $field['name'],
        $field['type'],
        $field['label']
      );

      $operation = $this->operationBuilder->buildOperation('create_field', $data);
      $result = $this->operationProcessor->executeOperation($operation);

      $this->assertExecutionResultSuccess($result);
    }

    // Verify all fields exist.
    foreach ($fields as $field) {
      $this->assertFieldConfigExists('node', 'article', $field['name']);
    }
  }

  /**
   * Tests field operation preview.
   */
  public function testFieldOperationPreview(): void {
    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_preview_test',
      'text_long',
      'Preview Test Field'
    );

    $operation = $this->operationBuilder->buildOperation('create_field', $data);
    $this->assertInstanceOf(PreviewableOperationInterface::class, $operation);
    $preview = $operation->preview();

    $this->assertPreviewHasOperations($preview);
    $this->assertPreviewHasOperationType($preview, 'create');

    // Details are added to the description via addDetails().
    $description = $preview->getDescription();
    $this->assertStringContainsString('Field Name: field_preview_test', $description);
    $this->assertStringContainsString('Field Type: text_long', $description);
  }

}
