<?php

declare(strict_types=1);

namespace Drupal\Tests\visitors\Unit\Plugin\VisitorsEvent;

use Drupal\Core\Config\Entity\ConfigEntityType;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\visitors\Plugin\VisitorsEvent\Entity;

/**
 * Unit tests for the Entity visitors event plugin.
 *
 * @group visitors
 * @coversDefaultClass \Drupal\visitors\Plugin\VisitorsEvent\Entity
 */
class EntityTest extends UnitTestCase {

  /**
   * The route match service.
   *
   * @var \PHPUnit\Framework\MockObject\MockObject|\Drupal\Core\Routing\RouteMatchInterface
   */
  protected $routeMatch;

  /**
   * The entity type manager.
   *
   * @var \PHPUnit\Framework\MockObject\MockObject|\Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The plugin instance being tested.
   *
   * @var \Drupal\visitors\Plugin\VisitorsEvent\Entity
   */
  protected $plugin;

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

    $this->routeMatch = $this->createMock(RouteMatchInterface::class);
    $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);

    $this->plugin = new Entity(
      [],
      'entity',
      ['id' => 'entity', 'label' => 'Entity'],
      $this->routeMatch,
      $this->entityTypeManager
    );
  }

  /**
   * Tests processing a non-entity route.
   *
   * @covers ::process
   */
  public function testProcessNonEntityRoute(): void {
    $context = [
      'route' => 'system.admin',
      'path' => '/admin',
    ];

    $result = $this->plugin->process($context);
    $this->assertNull($result);
  }

  /**
   * Tests processing a config entity route.
   *
   * @covers ::process
   */
  public function testProcessConfigEntity(): void {
    $entity_type = 'node_type';
    $entity_id = 'article';

    // Mock the entity.
    $entity = $this->createMock(EntityInterface::class);
    $entity->expects($this->once())
      ->method('id')
      ->willReturn($entity_id);

    // Mock the entity type definition.
    $entity_type_definition = $this->createMock(ConfigEntityType::class);

    // Set up route match expectations.
    $this->routeMatch->expects($this->once())
      ->method('getParameter')
      ->with($entity_type)
      ->willReturn($entity);

    // Set up entity type manager expectations.
    $this->entityTypeManager->expects($this->once())
      ->method('getDefinition')
      ->with($entity_type)
      ->willReturn($entity_type_definition);

    $context = [
      'route' => 'entity.node_type.edit_form',
      'path' => '/admin/structure/types/manage/article',
    ];

    $result = $this->plugin->process($context);

    $this->assertNotNull($result);
    $this->assertEquals('edit_form', $result['event']);
    $this->assertEquals($entity_type, $result['variables']['plugin_var_1']);
    $this->assertEquals($entity_id, $result['variables']['plugin_var_2']);
  }

  /**
   * Tests processing a content entity route.
   *
   * @covers ::process
   */
  public function testProcessContentEntity(): void {
    $entity_type = 'node';
    $bundle = 'article';
    $entity_id = '1';
    $revision_id = '2';

    // Mock the entity.
    $entity = $this->createMock(RevisionableInterface::class);
    $entity->expects($this->once())
      ->method('bundle')
      ->willReturn($bundle);
    $entity->expects($this->once())
      ->method('id')
      ->willReturn($entity_id);
    $entity->expects($this->once())
      ->method('getRevisionId')
      ->willReturn($revision_id);

    // Mock the entity type definition.
    $entity_type_definition = $this->createMock(ContentEntityType::class);
    $entity_type_definition->expects($this->once())
      ->method('isRevisionable')
      ->willReturn(TRUE);

    // Set up route match expectations.
    $this->routeMatch->expects($this->once())
      ->method('getParameter')
      ->with($entity_type)
      ->willReturn($entity);

    // Set up entity type manager expectations.
    $this->entityTypeManager->expects($this->once())
      ->method('getDefinition')
      ->with($entity_type)
      ->willReturn($entity_type_definition);

    $context = [
      'route' => 'entity.node.canonical',
      'path' => '/node/1',
    ];

    $result = $this->plugin->process($context);

    $this->assertNotNull($result);
    $this->assertEquals('canonical', $result['event']);
    $this->assertEquals($entity_type, $result['variables']['plugin_var_1']);
    $this->assertEquals($bundle, $result['variables']['plugin_var_2']);
    $this->assertEquals($entity_id, $result['variables']['plugin_int_1']);
    $this->assertEquals($revision_id, $result['variables']['plugin_int_2']);
  }

  /**
   * Tests processing a content entity with no bundle.
   *
   * @covers ::process
   */
  public function testProcessContentEntityNoBundleInfo(): void {
    $entity_type = 'user';

    // Mock the entity type definition.
    $entity_type_definition = $this->createMock(ContentEntityType::class);
    $entity_type_definition->expects($this->once())
      ->method('isRevisionable')
      ->willReturn(FALSE);

    // Set up route match expectations.
    $this->routeMatch->expects($this->once())
      ->method('getParameter')
      ->with($entity_type)
      ->willReturn(NULL);

    // Set up entity type manager expectations.
    $this->entityTypeManager->expects($this->once())
      ->method('getDefinition')
      ->with($entity_type)
      ->willReturn($entity_type_definition);

    $context = [
      'route' => 'entity.user.collection',
      'path' => '/admin/people',
    ];

    $result = $this->plugin->process($context);

    $this->assertNotNull($result);
    $this->assertEquals('collection', $result['event']);
    $this->assertEquals($entity_type, $result['variables']['plugin_var_1']);
    $this->assertEquals($entity_type, $result['variables']['plugin_var_2']);
  }

  /**
   * Tests processing an entity form.
   *
   * @covers ::process
   */
  public function testProcessEntityForm(): void {
    $entity_type = 'node';
    $bundle = 'article';
    $entity_id = 1;
    $revision_id = 2;
    $form_id = 'node_article_edit_form';
    $base_form_id = 'node_form';

    // Mock the entity.
    $entity = $this->createMock(ContentEntityInterface::class);
    $entity->expects($this->once())
      ->method('bundle')
      ->willReturn($bundle);
    $entity->expects($this->once())
      ->method('id')
      ->willReturn($entity_id);

    $entity->expects($this->once())
      ->method('getRevisionId')
      ->willReturn($revision_id);

    // Mock the entity type definition.
    $entity_type_definition = $this->createMock(ContentEntityType::class);
    $entity_type_definition->expects($this->once())
      ->method('isRevisionable')
      ->willReturn(TRUE);

    // Set up route match expectations.
    $this->routeMatch->expects($this->once())
      ->method('getParameter')
      ->with($entity_type)
      ->willReturn($entity);

    // Set up entity type manager expectations.
    $this->entityTypeManager->expects($this->once())
      ->method('getDefinition')
      ->with($entity_type)
      ->willReturn($entity_type_definition);

    // Set up form information in drupal_static.
    $forms = &drupal_static('visitors_form_alter');
    $forms = [];
    $forms[$form_id] = $base_form_id;

    $context = [
      'route' => 'entity.node.edit_form',
      'path' => '/node/1/edit',
    ];

    $result = $this->plugin->process($context);

    $this->assertNotNull($result);
    $this->assertEquals('edit_form', $result['event']);

    $this->assertEquals($entity_id, $result['variables']['plugin_int_1']);
    $this->assertEquals($revision_id, $result['variables']['plugin_int_2']);

    $this->assertEquals($entity_type, $result['variables']['plugin_var_1']);
    $this->assertEquals($bundle, $result['variables']['plugin_var_2']);

    $this->assertEquals($base_form_id, $result['variables']['plugin_var_3']);
    $this->assertEquals($form_id, $result['variables']['plugin_var_4']);

    // Clean up static variable.
    drupal_static_reset('visitors_form_alter');
  }

  /**
   * Tests processing with multiple forms.
   *
   * @covers ::process
   */
  public function testProcessMultipleForms(): void {
    $entity_type = 'node';
    $bundle = 'article';
    $entity_id = '1';

    // Mock the entity.
    $entity = $this->createMock(ContentEntityInterface::class);
    $entity->expects($this->once())
      ->method('bundle')
      ->willReturn($bundle);
    $entity->expects($this->once())
      ->method('id')
      ->willReturn($entity_id);

    // Mock the entity type definition.
    $entity_type_definition = $this->createMock(ContentEntityType::class);
    $entity_type_definition->expects($this->once())
      ->method('isRevisionable')
      ->willReturn(FALSE);

    // Set up route match expectations.
    $this->routeMatch->expects($this->once())
      ->method('getParameter')
      ->with($entity_type)
      ->willReturn($entity);

    // Set up entity type manager expectations.
    $this->entityTypeManager->expects($this->once())
      ->method('getDefinition')
      ->with($entity_type)
      ->willReturn($entity_type_definition);

    // Set up multiple forms in drupal_static.
    $forms = &drupal_static('visitors_form_alter', []);
    $forms['node_article_edit_form'] = 'node_form';
    $forms['another_form'] = 'other_form';

    $context = [
      'route' => 'entity.node.edit_form',
      'path' => '/node/1/edit',
    ];

    $result = $this->plugin->process($context);

    $this->assertNotNull($result);
    $this->assertEquals('edit_form', $result['event']);
    $this->assertEquals($entity_type, $result['variables']['plugin_var_1']);
    $this->assertEquals($bundle, $result['variables']['plugin_var_2']);
    $this->assertEquals($entity_id, $result['variables']['plugin_int_1']);
    $this->assertArrayNotHasKey('plugin_var_3', $result['variables']);
    $this->assertArrayNotHasKey('plugin_var_4', $result['variables']);

    // Clean up static variable.
    drupal_static_reset('visitors_form_alter');
  }

}
