<?php

namespace Drupal\Tests\eb\Kernel\Operation;

use Drupal\eb\PluginInterfaces\PreviewableOperationInterface;
use Drupal\eb\Service\OperationBuilder;
use Drupal\eb\Service\OperationProcessor;
use Drupal\node\Entity\NodeType;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\eb\Kernel\EbKernelTestBase;
use Drupal\Tests\eb\Traits\BundleCreationTrait;
use Drupal\Tests\eb\Traits\OperationTestTrait;
use Drupal\Tests\eb\Traits\ValidationAssertionsTrait;

/**
 * Kernel tests for bundle operations (create, update, delete).
 *
 * @group eb
 */
class BundleOperationsKernelTest extends EbKernelTestBase {

  use BundleCreationTrait;
  use OperationTestTrait;
  use ValidationAssertionsTrait;

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

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

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

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

  /**
   * Tests creating a node type bundle.
   */
  public function testCreateNodeTypeBundle(): void {
    $data = $this->createBundleOperationData('node', 'test_article', 'Test Article', [
      'description' => 'A test article content type.',
    ]);

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

    // Validate first.
    $validationResult = $operation->validate();
    $this->assertValidationResultPasses($validationResult);

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

    // Verify bundle was created.
    $this->assertNodeTypeExists('test_article');
    $nodeType = NodeType::load('test_article');
    $this->assertEquals('Test Article', $nodeType->label());
    $this->assertEquals('A test article content type.', $nodeType->getDescription());
  }

  /**
   * Tests creating a taxonomy vocabulary bundle.
   */
  public function testCreateTaxonomyVocabularyBundle(): void {
    $data = $this->createBundleOperationData('taxonomy_term', 'test_tags', 'Test Tags', [
      'description' => 'A test tags vocabulary.',
    ]);

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

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

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

    $this->assertVocabularyExists('test_tags');
    $vocabulary = Vocabulary::load('test_tags');
    $this->assertEquals('Test Tags', $vocabulary->label());
  }

  /**
   * Tests create bundle validation fails for existing bundle.
   */
  public function testCreateBundleFailsForExistingBundle(): void {
    // First create the bundle.
    $this->createTestNodeType('existing_type', 'Existing Type');

    // Try to create it again.
    $data = $this->createBundleOperationData('node', 'existing_type', 'Duplicate Type');

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

    $this->assertValidationResultFails($validationResult);
    $this->assertValidationHasErrorCode($validationResult, 'bundle_already_exists');
  }

  /**
   * Tests create bundle validation fails for invalid entity type.
   */
  public function testCreateBundleFailsForInvalidEntityType(): void {
    $data = $this->createBundleOperationData('nonexistent_entity_type', 'test', 'Test');

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

    $this->assertValidationResultFails($validationResult);
    $this->assertValidationHasErrorCode($validationResult, 'invalid_entity_type');
  }

  /**
   * Tests updating a node type bundle.
   */
  public function testUpdateNodeTypeBundle(): void {
    // Create initial bundle.
    $this->createTestNodeType('update_test', 'Update Test', [
      'description' => 'Original description.',
    ]);

    $data = [
      'operation' => 'update_bundle',
      'entity_type' => 'node',
      'bundle_id' => 'update_test',
      'label' => 'Updated Test',
      'description' => 'Updated description.',
    ];

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

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

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

    // Verify bundle was updated.
    $nodeType = NodeType::load('update_test');
    $this->assertEquals('Updated Test', $nodeType->label());
    $this->assertEquals('Updated description.', $nodeType->getDescription());
  }

  /**
   * Tests update bundle validation fails for non-existing bundle.
   */
  public function testUpdateBundleFailsForNonExistingBundle(): void {
    $data = [
      'operation' => 'update_bundle',
      'entity_type' => 'node',
      'bundle_id' => 'nonexistent_bundle',
      'label' => 'Should Fail',
    ];

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

    $this->assertValidationResultFails($validationResult);
  }

  /**
   * Tests deleting a node type bundle.
   */
  public function testDeleteNodeTypeBundle(): void {
    // Create bundle to delete.
    $this->createTestNodeType('delete_test', 'Delete Test');
    $this->assertNodeTypeExists('delete_test');

    $data = [
      'operation' => 'delete_bundle',
      'entity_type' => 'node',
      'bundle_id' => 'delete_test',
    ];

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

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

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

    // Verify bundle was deleted.
    $this->assertNodeTypeNotExists('delete_test');
  }

  /**
   * Tests delete bundle validation fails for non-existing bundle.
   */
  public function testDeleteBundleFailsForNonExistingBundle(): void {
    $data = [
      'operation' => 'delete_bundle',
      'entity_type' => 'node',
      'bundle_id' => 'nonexistent_bundle',
    ];

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

    $this->assertValidationResultFails($validationResult);
  }

  /**
   * Tests creating bundle with settings.
   */
  public function testCreateBundleWithSettings(): void {
    $data = $this->createBundleOperationData('node', 'with_settings', 'With Settings', [
      'settings' => [
        'new_revision' => FALSE,
        'preview_mode' => 0,
      ],
    ]);

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

    $this->assertExecutionResultSuccess($result);
    $this->assertNodeTypeExists('with_settings');
  }

  /**
   * Tests bundle creation rollback data.
   */
  public function testBundleCreationRollbackData(): void {
    $data = $this->createBundleOperationData('node', 'rollback_test', 'Rollback Test');

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

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

    $rollbackData = $result->getRollbackData();
    $this->assertEquals('node_type', $rollbackData['bundle_entity_type']);
    $this->assertEquals('rollback_test', $rollbackData['bundle_id']);
    $this->assertEquals('node', $rollbackData['entity_type']);
  }

  /**
   * Tests bundle operation preview.
   */
  public function testBundleOperationPreview(): void {
    $data = $this->createBundleOperationData('node', 'preview_test', 'Preview Test', [
      'description' => 'Description for preview.',
    ]);

    $operation = $this->operationBuilder->buildOperation('create_bundle', $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('Entity Type: node', $description);
    $this->assertStringContainsString('Bundle ID: preview_test', $description);
  }

  /**
   * Tests creating multiple bundles in sequence.
   */
  public function testCreateMultipleBundlesInSequence(): void {
    $bundles = [
      ['id' => 'seq_article', 'label' => 'Sequential Article'],
      ['id' => 'seq_page', 'label' => 'Sequential Page'],
      ['id' => 'seq_blog', 'label' => 'Sequential Blog'],
    ];

    foreach ($bundles as $bundle) {
      $data = $this->createBundleOperationData('node', $bundle['id'], $bundle['label']);
      $operation = $this->operationBuilder->buildOperation('create_bundle', $data);
      $result = $this->operationProcessor->executeOperation($operation);

      $this->assertExecutionResultSuccess($result);
      $this->assertNodeTypeExists($bundle['id']);
    }

    // Verify all bundles exist.
    foreach ($bundles as $bundle) {
      $nodeType = NodeType::load($bundle['id']);
      $this->assertNotNull($nodeType);
      $this->assertEquals($bundle['label'], $nodeType->label());
    }
  }

  /**
   * Tests creating taxonomy vocabulary with hierarchy setting.
   */
  public function testCreateTaxonomyVocabularyWithHierarchy(): void {
    $data = $this->createBundleOperationData('taxonomy_term', 'hierarchical_tags', 'Hierarchical Tags', [
      'description' => 'Tags with hierarchy support.',
    ]);

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

    $this->assertExecutionResultSuccess($result);
    $this->assertVocabularyExists('hierarchical_tags');
  }

}
