<?php

namespace Drupal\Tests\eb\Kernel\Service;

use Drupal\eb\Event\OperationEvents;
use Drupal\eb\Event\OperationPostExecuteEvent;
use Drupal\eb\Event\OperationPreExecuteEvent;
use Drupal\eb\Service\OperationBuilder;
use Drupal\eb\Service\OperationProcessor;
use Drupal\Tests\eb\Kernel\EbKernelTestBase;
use Drupal\Tests\eb\Traits\OperationTestTrait;

/**
 * Kernel tests for OperationProcessor service.
 *
 * @coversDefaultClass \Drupal\eb\Service\OperationProcessor
 * @group eb
 */
class OperationProcessorKernelTest extends EbKernelTestBase {

  use OperationTestTrait;

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

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

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

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

  /**
   * Tests executing a create_bundle operation creates a node type.
   *
   * @covers ::executeOperation
   */
  public function testExecuteCreateBundleOperation(): void {
    $data = $this->createBundleOperationData(
      'node',
      'test_article',
      'Test Article'
    );

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

    $this->assertExecutionSuccess($result);
    $this->assertNodeTypeExists('test_article');
    $this->assertHasRollbackData($result);
  }

  /**
   * Tests executing a create_bundle operation for taxonomy vocabulary.
   *
   * @covers ::executeOperation
   */
  public function testExecuteCreateTaxonomyBundleOperation(): void {
    $data = $this->createBundleOperationData(
      'taxonomy_term',
      'test_tags',
      'Test Tags'
    );

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

    $this->assertExecutionSuccess($result);
    $this->assertVocabularyExists('test_tags');
  }

  /**
   * Tests executing a create_field operation creates a field.
   *
   * @covers ::executeOperation
   */
  public function testExecuteCreateFieldOperation(): void {
    // First create the bundle.
    $this->createNodeType('article', 'Article');

    $data = $this->createFieldOperationData(
      'node',
      'article',
      'field_subtitle',
      'text',
      'Subtitle'
    );

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

    $this->assertExecutionSuccess($result);
    $this->assertFieldStorageExists('node', 'field_subtitle');
    $this->assertFieldConfigExists('node', 'article', 'field_subtitle');
  }

  /**
   * Tests operation execution dispatches pre-execute event.
   *
   * @covers ::executeOperation
   */
  public function testExecuteOperationDispatchesPreExecuteEvent(): void {
    $preEventFired = FALSE;
    $capturedOperation = NULL;

    $this->container->get('event_dispatcher')->addListener(
      OperationEvents::PRE_EXECUTE,
      function (OperationPreExecuteEvent $event) use (&$preEventFired, &$capturedOperation) {
        $preEventFired = TRUE;
        $capturedOperation = $event->getOperation();
      }
    );

    $data = $this->createBundleOperationData('node', 'event_test', 'Event Test');
    $operation = $this->operationBuilder->buildOperation('create_bundle', $data);
    $this->operationProcessor->executeOperation($operation);

    $this->assertTrue($preEventFired, 'Pre-execute event should be dispatched.');
    $this->assertNotNull($capturedOperation);
  }

  /**
   * Tests operation execution dispatches post-execute event.
   *
   * @covers ::executeOperation
   */
  public function testExecuteOperationDispatchesPostExecuteEvent(): void {
    $postEventFired = FALSE;
    $capturedResult = NULL;

    $this->container->get('event_dispatcher')->addListener(
      OperationEvents::POST_EXECUTE,
      function (OperationPostExecuteEvent $event) use (&$postEventFired, &$capturedResult) {
        $postEventFired = TRUE;
        $capturedResult = $event->getResult();
      }
    );

    $data = $this->createBundleOperationData('node', 'post_event_test', 'Post Event Test');
    $operation = $this->operationBuilder->buildOperation('create_bundle', $data);
    $this->operationProcessor->executeOperation($operation);

    $this->assertTrue($postEventFired, 'Post-execute event should be dispatched.');
    $this->assertNotNull($capturedResult);
    $this->assertTrue($capturedResult->isSuccess());
  }

  /**
   * Tests operation can be cancelled via pre-execute event.
   *
   * @covers ::executeOperation
   */
  public function testOperationCanBeCancelledViaEvent(): void {
    $this->container->get('event_dispatcher')->addListener(
      OperationEvents::PRE_EXECUTE,
      function (OperationPreExecuteEvent $event) {
        $event->cancel('Cancelled by test');
      }
    );

    $data = $this->createBundleOperationData('node', 'cancelled_bundle', 'Cancelled Bundle');
    $operation = $this->operationBuilder->buildOperation('create_bundle', $data);
    $result = $this->operationProcessor->executeOperation($operation);

    $this->assertExecutionFailure($result);
    $this->assertNodeTypeNotExists('cancelled_bundle');
  }

  /**
   * Tests executeBatch processes multiple operations.
   *
   * @covers ::executeBatch
   */
  public function testExecuteBatchProcessesMultipleOperations(): void {
    $operations = [];

    // Create bundle operation.
    $bundleData = $this->createBundleOperationData('node', 'batch_test', 'Batch Test');
    $operations[] = $this->operationBuilder->buildOperation('create_bundle', $bundleData);

    // Create field operation (depends on bundle).
    $fieldData = $this->createFieldOperationData(
      'node',
      'batch_test',
      'field_batch_test',
      'text',
      'Batch Test Field'
    );
    $operations[] = $this->operationBuilder->buildOperation('create_field', $fieldData);

    $results = $this->operationProcessor->executeBatch($operations);

    $this->assertCount(2, $results);
    $this->assertTrue($results[0]->isSuccess());
    $this->assertTrue($results[1]->isSuccess());

    $this->assertNodeTypeExists('batch_test');
    $this->assertFieldConfigExists('node', 'batch_test', 'field_batch_test');
  }

  /**
   * Tests executeBatch stops on failure with stop_on_failure=true.
   *
   * @covers ::executeBatch
   */
  public function testExecuteBatchStopsOnFailure(): void {
    $operations = [];

    // Valid bundle creation.
    $bundleData = $this->createBundleOperationData('node', 'stop_test', 'Stop Test');
    $operations[] = $this->operationBuilder->buildOperation('create_bundle', $bundleData);

    // Invalid field (non-existent bundle).
    $fieldData = $this->createFieldOperationData(
      'node',
      'nonexistent_bundle',
      'field_test',
      'text',
      'Test Field'
    );
    $operations[] = $this->operationBuilder->buildOperation('create_field', $fieldData);

    // This won't be executed because previous fails.
    $bundleData2 = $this->createBundleOperationData('node', 'never_created', 'Never Created');
    $operations[] = $this->operationBuilder->buildOperation('create_bundle', $bundleData2);

    $results = $this->operationProcessor->executeBatch($operations, TRUE);

    // First operation should succeed, second should fail, third not executed.
    $this->assertTrue($results[0]->isSuccess());
    $this->assertFalse($results[1]->isSuccess());
    $this->assertArrayNotHasKey(2, $results);

    $this->assertNodeTypeExists('stop_test');
    $this->assertNodeTypeNotExists('never_created');
  }

  /**
   * Tests executeBatch continues on failure with stop_on_failure=false.
   *
   * @covers ::executeBatch
   */
  public function testExecuteBatchContinuesOnFailure(): void {
    $operations = [];

    // Valid bundle creation.
    $bundleData1 = $this->createBundleOperationData('node', 'continue_test_1', 'Continue Test 1');
    $operations[] = $this->operationBuilder->buildOperation('create_bundle', $bundleData1);

    // Invalid field (non-existent bundle).
    $fieldData = $this->createFieldOperationData(
      'node',
      'nonexistent_bundle',
      'field_test',
      'text',
      'Test Field'
    );
    $operations[] = $this->operationBuilder->buildOperation('create_field', $fieldData);

    // This WILL be executed because stop_on_failure is false.
    $bundleData2 = $this->createBundleOperationData('node', 'continue_test_2', 'Continue Test 2');
    $operations[] = $this->operationBuilder->buildOperation('create_bundle', $bundleData2);

    $results = $this->operationProcessor->executeBatch($operations, FALSE);

    $this->assertCount(3, $results);
    $this->assertTrue($results[0]->isSuccess());
    $this->assertFalse($results[1]->isSuccess());
    $this->assertTrue($results[2]->isSuccess());

    $this->assertNodeTypeExists('continue_test_1');
    $this->assertNodeTypeExists('continue_test_2');
  }

  /**
   * Tests successful operations store rollback data.
   *
   * @covers ::executeOperation
   */
  public function testSuccessfulOperationStoresRollbackData(): void {
    $data = $this->createBundleOperationData('node', 'rollback_test', 'Rollback Test');

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

    $this->assertExecutionSuccess($result);

    // Verify rollback data exists.
    $rollbackData = $result->getRollbackData();
    $this->assertNotEmpty($rollbackData);
    $this->assertArrayHasKey('bundle_entity_type', $rollbackData);
    $this->assertArrayHasKey('bundle_id', $rollbackData);
    $this->assertEquals('rollback_test', $rollbackData['bundle_id']);
  }

  /**
   * Tests operation result contains affected entities.
   *
   * @covers ::executeOperation
   */
  public function testOperationResultContainsAffectedEntities(): void {
    $data = $this->createBundleOperationData('node', 'affected_test', 'Affected Test');

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

    $this->assertExecutionSuccess($result);

    $affectedEntities = $result->getAffectedEntities();
    $this->assertNotEmpty($affectedEntities);
  }

  /**
   * Tests failed operation does not create entities.
   *
   * @covers ::executeOperation
   */
  public function testFailedOperationDoesNotCreateEntities(): void {
    // Try to create a bundle with invalid entity type.
    $data = $this->createBundleOperationData(
      'nonexistent_entity_type',
      'invalid_bundle',
      'Invalid Bundle'
    );

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

    // First validate - should fail.
    $validationResult = $operation->validate();
    $this->assertFalse($validationResult->isValid());
  }

}
