<?php

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

use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\eb\PluginBase\OperationBase;
use Drupal\eb\PluginInterfaces\FullOperationInterface;
use Drupal\eb\PluginInterfaces\OperationInterface;
use Drupal\eb\PluginInterfaces\PreviewableOperationInterface;
use Drupal\eb\PluginInterfaces\ReversibleOperationInterface;
use PHPUnit\Framework\TestCase;

/**
 * Tests for Operation interface hierarchy (Phase 3.3 ISP).
 *
 * Verifies that the interface hierarchy created in Phase 3.3 is correctly
 * structured and that OperationBase properly implements FullOperationInterface.
 *
 * @group eb
 */
class OperationInterfaceHierarchyTest extends TestCase {

  /**
   * Tests that OperationInterface extends PluginInspectionInterface.
   */
  public function testOperationInterfaceExtendsPluginInspection(): void {
    $reflection = new \ReflectionClass(OperationInterface::class);

    // OperationInterface should extend PluginInspectionInterface.
    $this->assertTrue($reflection->isSubclassOf(PluginInspectionInterface::class));

    // Verify it has the expected base methods.
    $this->assertTrue($reflection->hasMethod('validate'));
    $this->assertTrue($reflection->hasMethod('execute'));
    $this->assertTrue($reflection->hasMethod('checkAccess'));
    $this->assertTrue($reflection->hasMethod('getData'));
  }

  /**
   * Tests that PreviewableOperationInterface extends OperationInterface.
   */
  public function testPreviewableOperationInterfaceExtendsOperation(): void {
    $reflection = new \ReflectionClass(PreviewableOperationInterface::class);

    $this->assertTrue($reflection->isSubclassOf(OperationInterface::class));
    $this->assertTrue($reflection->hasMethod('preview'));
  }

  /**
   * Tests that ReversibleOperationInterface extends OperationInterface.
   */
  public function testReversibleOperationInterfaceExtendsOperation(): void {
    $reflection = new \ReflectionClass(ReversibleOperationInterface::class);

    $this->assertTrue($reflection->isSubclassOf(OperationInterface::class));
    $this->assertTrue($reflection->hasMethod('rollback'));
  }

  /**
   * Tests that FullOperationInterface extends both specialized interfaces.
   */
  public function testFullOperationInterfaceExtendsBoth(): void {
    $reflection = new \ReflectionClass(FullOperationInterface::class);

    $this->assertTrue($reflection->isSubclassOf(PreviewableOperationInterface::class));
    $this->assertTrue($reflection->isSubclassOf(ReversibleOperationInterface::class));
    $this->assertTrue($reflection->isSubclassOf(OperationInterface::class));
  }

  /**
   * Tests that OperationBase implements FullOperationInterface.
   *
   * @covers \Drupal\eb\PluginBase\OperationBase
   */
  public function testOperationBaseImplementsFullOperationInterface(): void {
    $reflection = new \ReflectionClass(OperationBase::class);

    $this->assertTrue($reflection->implementsInterface(FullOperationInterface::class));
    $this->assertTrue($reflection->implementsInterface(PreviewableOperationInterface::class));
    $this->assertTrue($reflection->implementsInterface(ReversibleOperationInterface::class));
    $this->assertTrue($reflection->implementsInterface(OperationInterface::class));
  }

  /**
   * Tests that OperationBase has all required methods from all interfaces.
   *
   * @covers \Drupal\eb\PluginBase\OperationBase
   */
  public function testOperationBaseHasAllRequiredMethods(): void {
    $reflection = new \ReflectionClass(OperationBase::class);

    // From OperationInterface.
    $this->assertTrue($reflection->hasMethod('validate'));
    $this->assertTrue($reflection->hasMethod('execute'));
    $this->assertTrue($reflection->hasMethod('checkAccess'));
    $this->assertTrue($reflection->hasMethod('getData'));

    // From PreviewableOperationInterface.
    $this->assertTrue($reflection->hasMethod('preview'));

    // From ReversibleOperationInterface.
    $this->assertTrue($reflection->hasMethod('rollback'));
  }

  /**
   * Tests that interfaces do not require methods from sibling interfaces.
   *
   * Validates ISP - each specialized interface only requires its own method.
   */
  public function testPreviewableInterfaceOnlyAddsPreview(): void {
    $reflectionPreviewable = new \ReflectionClass(PreviewableOperationInterface::class);

    // Get methods declared directly on PreviewableOperationInterface.
    $previewableMethods = array_filter(
      $reflectionPreviewable->getMethods(),
      fn ($method) => $method->getDeclaringClass()->getName() === PreviewableOperationInterface::class
    );

    // Should only declare preview() method.
    $this->assertCount(1, $previewableMethods);
    $this->assertEquals('preview', array_values($previewableMethods)[0]->getName());
  }

  /**
   * Tests that ReversibleOperationInterface only adds rollback method.
   *
   * This validates ISP - the interface only requires its own specific method.
   */
  public function testReversibleInterfaceOnlyAddsRollback(): void {
    $reflectionReversible = new \ReflectionClass(ReversibleOperationInterface::class);

    // Get methods declared directly on ReversibleOperationInterface.
    $reversibleMethods = array_filter(
      $reflectionReversible->getMethods(),
      fn ($method) => $method->getDeclaringClass()->getName() === ReversibleOperationInterface::class
    );

    // Should only declare rollback() method.
    $this->assertCount(1, $reversibleMethods);
    $this->assertEquals('rollback', array_values($reversibleMethods)[0]->getName());
  }

  /**
   * Tests that FullOperationInterface does not declare any new methods.
   *
   * FullOperationInterface combines PreviewableOperationInterface and
   * ReversibleOperationInterface without adding new requirements.
   */
  public function testFullOperationInterfaceDeclaresNoNewMethods(): void {
    $reflection = new \ReflectionClass(FullOperationInterface::class);

    // Get methods declared directly on FullOperationInterface.
    $fullMethods = array_filter(
      $reflection->getMethods(),
      fn ($method) => $method->getDeclaringClass()->getName() === FullOperationInterface::class
    );

    // Should not declare any new methods.
    $this->assertCount(0, $fullMethods);
  }

}
