<?php

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

use Drupal\eb\Plugin\EbValidator\FormatterCompatibilityValidator;
use Drupal\eb\Service\DiscoveryServiceInterface;
use Drupal\Tests\eb\Traits\ValidationAssertionsTrait;
use Drupal\Tests\eb\Unit\EbUnitTestBase;
use PHPUnit\Framework\MockObject\MockObject;

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

  use ValidationAssertionsTrait;

  /**
   * Mock discovery service.
   */
  protected DiscoveryServiceInterface|MockObject $discoveryService;

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

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

    $this->discoveryService = $this->createMock(DiscoveryServiceInterface::class);

    $this->validator = new FormatterCompatibilityValidator(
      [],
      'formatter_compatibility',
      [
        'id' => 'formatter_compatibility',
        'label' => 'Formatter Compatibility Validator',
      ],
      $this->discoveryService
    );
  }

  /**
   * Tests validation passes when formatter is compatible.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenFormatterCompatible(): void {
    $this->discoveryService
      ->method('formatterExists')
      ->with('string')
      ->willReturn(TRUE);

    $this->discoveryService
      ->method('getFormattersForFieldType')
      ->with('string')
      ->willReturn([
        'string' => ['id' => 'string', 'label' => 'Plain text'],
        'string_trim' => ['id' => 'string_trim', 'label' => 'Trimmed'],
      ]);

    $data = [
      'field_type' => 'string',
      'formatter' => 'string',
    ];

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

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation fails when formatter does not exist.
   *
   * @covers ::validate
   */
  public function testValidateFailsWhenFormatterNotFound(): void {
    $this->discoveryService
      ->method('formatterExists')
      ->with('nonexistent')
      ->willReturn(FALSE);

    $data = [
      'field_type' => 'string',
      'formatter' => 'nonexistent',
    ];

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

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

  /**
   * Tests validation fails when formatter is not compatible.
   *
   * @covers ::validate
   */
  public function testValidateFailsWhenFormatterNotCompatible(): void {
    $this->discoveryService
      ->method('formatterExists')
      ->with('boolean')
      ->willReturn(TRUE);

    $this->discoveryService
      ->method('getFormattersForFieldType')
      ->with('string')
      ->willReturn([
        'string' => ['id' => 'string', 'label' => 'Plain text'],
      ]);

    $data = [
      'field_type' => 'string',
      'formatter' => 'boolean',
    ];

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

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

  /**
   * Tests validation passes when field_type is not specified.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenNoFieldType(): void {
    $data = [
      'formatter' => 'string',
    ];

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

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation passes when formatter is not specified.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenNoFormatter(): void {
    $data = [
      'field_type' => 'string',
    ];

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

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation with formatter as array with type key.
   *
   * @covers ::validate
   */
  public function testValidateWithFormatterAsArray(): void {
    $this->discoveryService
      ->method('formatterExists')
      ->with('string')
      ->willReturn(TRUE);

    $this->discoveryService
      ->method('getFormattersForFieldType')
      ->with('string')
      ->willReturn([
        'string' => ['id' => 'string', 'label' => 'Plain text'],
      ]);

    $data = [
      'field_type' => 'string',
      'formatter' => [
        'type' => 'string',
        'settings' => ['link_to_entity' => FALSE],
      ],
    ];

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

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation passes when formatter array has no type.
   *
   * @covers ::validate
   */
  public function testValidatePassesWhenFormatterArrayNoType(): void {
    $data = [
      'field_type' => 'string',
      'formatter' => [
        'settings' => ['link_to_entity' => FALSE],
      ],
    ];

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

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests error message includes compatible formatters.
   *
   * @covers ::validate
   */
  public function testValidateErrorIncludesCompatibleFormatters(): void {
    $this->discoveryService
      ->method('formatterExists')
      ->with('boolean')
      ->willReturn(TRUE);

    $this->discoveryService
      ->method('getFormattersForFieldType')
      ->with('string')
      ->willReturn([
        'string' => ['id' => 'string', 'label' => 'Plain text'],
        'string_trim' => ['id' => 'string_trim', 'label' => 'Trimmed'],
      ]);

    $data = [
      'field_type' => 'string',
      'formatter' => 'boolean',
    ];

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

    // Error message should include compatible formatters.
    $this->assertStringContainsString('string', (string) $errors[0]['message']);
  }

  /**
   * Tests validation with empty field_type.
   *
   * @covers ::validate
   */
  public function testValidatePassesWithEmptyFieldType(): void {
    $data = [
      'field_type' => '',
      'formatter' => 'string',
    ];

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

    $this->assertValidationResultPasses($result);
  }

  /**
   * Tests validation with empty formatter.
   *
   * @covers ::validate
   */
  public function testValidatePassesWithEmptyFormatter(): void {
    $data = [
      'field_type' => 'string',
      'formatter' => '',
    ];

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

    $this->assertValidationResultPasses($result);
  }

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

}
