<?php

namespace Drupal\Tests\eb\Unit\Plugin\EbValidator;

use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\eb\Plugin\EbValidator\DependencyValidator;
use Drupal\Tests\eb\Traits\ValidationAssertionsTrait;
use Drupal\Tests\eb\Unit\EbUnitTestBase;
use PHPUnit\Framework\MockObject\MockObject;

/**
 * Unit tests for DependencyValidator plugin.
 *
 * @coversDefaultClass \Drupal\eb\Plugin\EbValidator\DependencyValidator
 * @group eb
 */
class DependencyValidatorTest extends EbUnitTestBase {

  use ValidationAssertionsTrait;

  /**
   * Mock entity type manager.
   */
  protected EntityTypeManagerInterface|MockObject $entityTypeManager;

  /**
   * Mock entity field manager.
   */
  protected EntityFieldManagerInterface|MockObject $entityFieldManager;

  /**
   * The validator under test.
   */
  protected DependencyValidator $validator;

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

    $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
    $this->entityFieldManager = $this->createMock(EntityFieldManagerInterface::class);

    $this->validator = new DependencyValidator(
      [],
      'dependency',
      [
        'id' => 'dependency',
        'label' => 'Dependency Validator',
      ],
      $this->entityTypeManager,
      $this->entityFieldManager
    );
  }

  /**
   * Tests validation passes when entity type exists.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenEntityTypeExists(): void {
    $entityType = $this->createMock(EntityTypeInterface::class);
    $this->entityTypeManager
      ->method('getDefinition')
      ->with('node')
      ->willReturn($entityType);

    $data = ['entity_type' => 'node'];
    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation fails when entity type does not exist.
   *
   * @covers ::validate
   */
  public function testValidateFailsWhenEntityTypeDoesNotExist(): void {
    $this->entityTypeManager
      ->method('getDefinition')
      ->with('nonexistent')
      ->willThrowException(new \Exception('Entity type not found'));

    $data = ['entity_type' => 'nonexistent'];
    $result = $this->validator->validate($data);

    $this->assertValidationResultFails($result);
    $this->assertValidationHasErrorCode($result, 'entity_type_not_found');
  }

  /**
   * Tests validation passes when bundle exists.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenBundleExists(): void {
    $entityType = $this->createMock(EntityTypeInterface::class);
    $entityType->method('getBundleEntityType')->willReturn('node_type');

    $this->entityTypeManager
      ->method('getDefinition')
      ->with('node')
      ->willReturn($entityType);

    $bundleStorage = $this->createMock(EntityStorageInterface::class);
    $bundleStorage->method('load')
      ->with('article')
      ->willReturn((object) ['id' => 'article']);

    $this->entityTypeManager
      ->method('getStorage')
      ->with('node_type')
      ->willReturn($bundleStorage);

    $data = [
      'entity_type' => 'node',
      'bundle' => 'article',
      'field_name' => 'field_test',
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation fails when bundle does not exist.
   *
   * @covers ::validate
   */
  public function testValidateFailsWhenBundleDoesNotExist(): void {
    $entityType = $this->createMock(EntityTypeInterface::class);
    $entityType->method('getBundleEntityType')->willReturn('node_type');

    $this->entityTypeManager
      ->method('getDefinition')
      ->with('node')
      ->willReturn($entityType);

    $bundleStorage = $this->createMock(EntityStorageInterface::class);
    $bundleStorage->method('load')
      ->with('nonexistent')
      ->willReturn(NULL);

    $this->entityTypeManager
      ->method('getStorage')
      ->with('node_type')
      ->willReturn($bundleStorage);

    $data = [
      'entity_type' => 'node',
      'bundle' => 'nonexistent',
      'field_name' => 'field_test',
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultFails($result);
    $this->assertValidationHasErrorCode($result, 'bundle_not_found');
  }

  /**
   * Tests validation passes when bundle will be created in batch.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenBundleInBatch(): void {
    $entityType = $this->createMock(EntityTypeInterface::class);
    $this->entityTypeManager
      ->method('getDefinition')
      ->with('node')
      ->willReturn($entityType);

    $data = [
      'entity_type' => 'node',
      'bundle' => 'new_bundle',
      'field_name' => 'field_test',
      '_batch_context' => [
        'bundle_definitions' => [
          'node' => ['new_bundle' => 0],
        ],
      ],
      '_batch_index' => 1,
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation fails when field does not exist for update_field.
   *
   * @covers ::validate
   */
  public function testValidateFailsWhenFieldDoesNotExistForUpdate(): void {
    $entityType = $this->createMock(EntityTypeInterface::class);
    $entityType->method('getBundleEntityType')->willReturn('node_type');

    $this->entityTypeManager
      ->method('getDefinition')
      ->with('node')
      ->willReturn($entityType);

    $bundleStorage = $this->createMock(EntityStorageInterface::class);
    $bundleStorage->method('load')
      ->with('article')
      ->willReturn((object) ['id' => 'article']);

    $this->entityTypeManager
      ->method('getStorage')
      ->with('node_type')
      ->willReturn($bundleStorage);

    $this->entityFieldManager
      ->method('getFieldMap')
      ->willReturn([
        'node' => [
          'field_existing' => ['bundles' => ['article']],
        ],
      ]);

    $data = [
      'entity_type' => 'node',
      'bundle' => 'article',
      'field_name' => 'field_nonexistent',
      'operation_type' => 'update_field',
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultFails($result);
    $this->assertValidationHasErrorCode($result, 'field_not_found');
  }

  /**
   * Tests validation passes when field exists for update_field.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenFieldExistsForUpdate(): void {
    $entityType = $this->createMock(EntityTypeInterface::class);
    $entityType->method('getBundleEntityType')->willReturn('node_type');

    $this->entityTypeManager
      ->method('getDefinition')
      ->with('node')
      ->willReturn($entityType);

    $bundleStorage = $this->createMock(EntityStorageInterface::class);
    $bundleStorage->method('load')
      ->with('article')
      ->willReturn((object) ['id' => 'article']);

    $this->entityTypeManager
      ->method('getStorage')
      ->with('node_type')
      ->willReturn($bundleStorage);

    $this->entityFieldManager
      ->method('getFieldMap')
      ->willReturn([
        'node' => [
          'field_existing' => ['bundles' => ['article']],
        ],
      ]);

    $data = [
      'entity_type' => 'node',
      'bundle' => 'article',
      'field_name' => 'field_existing',
      'operation_type' => 'update_field',
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation fails when menu does not exist.
   *
   * @covers ::validate
   */
  public function testValidateFailsWhenMenuDoesNotExist(): void {
    $menuStorage = $this->createMock(EntityStorageInterface::class);
    $menuStorage->method('load')
      ->with('nonexistent_menu')
      ->willReturn(NULL);

    $this->entityTypeManager
      ->method('getStorage')
      ->with('menu')
      ->willReturn($menuStorage);

    $data = [
      'menu_id' => 'nonexistent_menu',
      'operation_type' => 'create_menu_link',
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultFails($result);
    $this->assertValidationHasErrorCode($result, 'menu_not_found');
  }

  /**
   * Tests validation passes when menu will be created in batch.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenMenuInBatch(): void {
    $data = [
      'menu_id' => 'new_menu',
      'operation_type' => 'create_menu_link',
      '_batch_context' => [
        'menu_definitions' => ['new_menu' => 0],
      ],
      '_batch_index' => 1,
    ];

    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests getPluginId returns correct ID.
   *
   * @covers ::getPluginId
   */
  public function testGetPluginIdReturnsCorrectId(): void {
    $this->assertEquals('dependency', $this->validator->getPluginId());
  }

}
