<?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\UniqueNameValidator;
use Drupal\Tests\eb\Traits\ValidationAssertionsTrait;
use Drupal\Tests\eb\Unit\EbUnitTestBase;
use PHPUnit\Framework\MockObject\MockObject;

/**
 * Unit tests for UniqueNameValidator plugin.
 *
 * @coversDefaultClass \Drupal\eb\Plugin\EbValidator\UniqueNameValidator
 * @group eb
 */
class UniqueNameValidatorTest 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 UniqueNameValidator $validator;

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

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

    $this->validator = new UniqueNameValidator(
      [],
      'unique_name',
      [
        'id' => 'unique_name',
        'label' => 'Unique Name Validator',
      ],
      $this->entityTypeManager,
      $this->entityFieldManager
    );
  }

  /**
   * Tests validation passes when bundle does not exist.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenBundleDoesNotExist(): 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('new_bundle')
      ->willReturn(NULL);

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

    $data = [
      'entity_type' => 'node',
      'bundle_id' => 'new_bundle',
    ];

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

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation fails when bundle already exists.
   *
   * @covers ::validate
   */
  public function testValidateFailsWhenBundleExists(): 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('existing_bundle')
      ->willReturn((object) ['id' => 'existing_bundle']);

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

    $data = [
      'entity_type' => 'node',
      'bundle_id' => 'existing_bundle',
    ];

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

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

  /**
   * Tests validation passes when field does not exist.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenFieldDoesNotExist(): void {
    $this->entityFieldManager
      ->method('getFieldMap')
      ->willReturn([
        'node' => [
          'field_other' => ['bundles' => ['article']],
        ],
      ]);

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

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

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation fails when field already exists on bundle.
   *
   * @covers ::validate
   */
  public function testValidateFailsWhenFieldExistsOnBundle(): void {
    $this->entityFieldManager
      ->method('getFieldMap')
      ->willReturn([
        'node' => [
          'field_existing' => ['bundles' => ['article', 'page']],
        ],
      ]);

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

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

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

  /**
   * Tests validation passes when field exists on different bundle.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenFieldExistsOnDifferentBundle(): void {
    $this->entityFieldManager
      ->method('getFieldMap')
      ->willReturn([
        'node' => [
          'field_existing' => ['bundles' => ['page']],
        ],
      ]);

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

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

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation passes with empty data.
   *
   * @covers ::validate
   */
  public function testValidatePassesWithEmptyData(): void {
    $data = [];
    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation passes when entity type doesn't exist.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenEntityTypeThrows(): void {
    $this->entityTypeManager
      ->method('getDefinition')
      ->with('invalid')
      ->willThrowException(new \Exception('Not found'));

    $data = [
      'entity_type' => 'invalid',
      'bundle_id' => 'test',
    ];

    // Should pass because other validators handle this case.
    $result = $this->validator->validate($data);

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation error contains bundle name.
   *
   * @covers ::validate
   */
  public function testValidateErrorContainsBundleName(): void {
    $entityType = $this->createMock(EntityTypeInterface::class);
    $entityType->method('getBundleEntityType')->willReturn('node_type');

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

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

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

    $data = [
      'entity_type' => 'node',
      'bundle_id' => 'duplicate',
    ];

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

    $this->assertStringContainsString('duplicate', (string) $errors[0]['message']);
  }

  /**
   * Tests validation error contains field name.
   *
   * @covers ::validate
   */
  public function testValidateErrorContainsFieldName(): void {
    $this->entityFieldManager
      ->method('getFieldMap')
      ->willReturn([
        'node' => [
          'field_duplicate' => ['bundles' => ['article']],
        ],
      ]);

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

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

    $this->assertStringContainsString('field_duplicate', (string) $errors[0]['message']);
  }

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

}
