<?php

declare(strict_types=1);

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

use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Form\FormState;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Session\SessionManager;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\user\Entity\User;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\Entity\Term;
use Symfony\Component\Routing\Route;
use Drupal\visitors\Plugin\VisitorsEvent\Entity;

/**
 * Tests the Entity visitors event plugin.
 *
 * @group visitors
 * @coversDefaultClass \Drupal\visitors\Plugin\VisitorsEvent\Entity
 */
class EntityTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'user',
    'node',
    'taxonomy',
    'field',
    'text',
    'visitors',
    'path_alias',
  ];

  /**
   * The visitors event plugin manager.
   *
   * @var \Drupal\visitors\VisitorsEventPluginManager
   */
  protected $eventPluginManager;

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

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

    $this->installEntitySchema('user');
    $this->installEntitySchema('node');
    $this->installEntitySchema('taxonomy_term');
    $this->installSchema('system', ['sequences']);
    $this->installSchema('visitors', ['visitors_visit', 'visitors_event', 'visitors_counter']);
    $this->installConfig(['field', 'node', 'visitors']);

    // Create a node type.
    $node_type = NodeType::create([
      'type' => 'article',
      'name' => 'Article',
    ]);
    $node_type->save();

    // Create a vocabulary.
    $vocabulary = Vocabulary::create([
      'vid' => 'tags',
      'name' => 'Tags',
    ]);
    $vocabulary->save();

    // Create a test user.
    $user = User::create([
      'name' => $this->randomMachineName(),
      'mail' => 'test@example.com',
    ]);
    $user->save();
    $this->container->get('current_user')->setAccount($user);

    // Set up the request stack with a request object.
    $request = new Request();
    $request->server->set('REQUEST_URI', '/node/1');

    // Initialize session handling.
    $session_manager = $this->container->get('session_manager');
    if (!$session_manager instanceof SessionManager) {
      $session_manager = new SessionManager(
        $this->container->get('request_stack'),
        $this->container->get('database'),
        $this->container->get('session_manager.metadata_bag'),
        $this->container->get('session_configuration'),
        $this->container->get('session_handler')
      );
      $this->container->set('session_manager', $session_manager);
    }

    // Start the session.
    $session_manager->start();
    $request->setSession($this->container->get('session'));

    $this->container->get('request_stack')->push($request);

    $this->eventPluginManager = $this->container->get('visitors.event_plugin_manager');
    $this->entityTypeManager = $this->container->get('entity_type.manager');
  }

  /**
   * Tests processing a node entity.
   *
   * @covers ::process
   */
  public function testProcessWithRealNode(): void {
    // Create a node.
    $node = Node::create([
      'type' => 'article',
      'title' => 'Test article',
    ]);
    $node->save();

    // Create a mock route match.
    $route = new Route('/node/{node}');
    $route_match = new RouteMatch(
      'entity.node.canonical',
      $route,
      ['node' => $node],
      ['node' => $node]
    );
    $this->container->set('current_route_match', $route_match);

    /** @var \Drupal\visitors\Plugin\VisitorsEvent\Entity $plugin */
    $plugin = $this->eventPluginManager->createInstance('entity');

    // Test viewing a node.
    $context = [
      'route' => 'entity.node.canonical',
      'path' => '/node/' . $node->id(),
    ];
    $result = $plugin->process($context);

    $this->assertNotNull($result);
    $this->assertEquals('canonical', $result['event']);
    $this->assertEquals($node->id(), $result['variables']['plugin_int_1']);
    $this->assertEquals($node->getRevisionId(), $result['variables']['plugin_int_2']);
    $this->assertEquals('node', $result['variables']['plugin_var_1']);
    $this->assertEquals('article', $result['variables']['plugin_var_2']);
  }

  /**
   * Tests creating the plugin.
   *
   * @covers ::create
   */
  public function testCreate(): void {
    $plugin = Entity::create($this->container, [], 'entity', []);
    $this->assertInstanceOf(Entity::class, $plugin);
  }

  /**
   * Tests creating the plugin.
   *
   * @covers ::__construct
   */
  public function testConstructor(): void {
    $plugin = new Entity([], 'entity', [], $this->container->get('current_route_match'), $this->container->get('entity_type.manager'));
    $this->assertInstanceOf(Entity::class, $plugin);
  }

  /**
   * Tests processing a user entity.
   *
   * @covers ::process
   */
  public function testProcessWithRealUser(): void {
    // Create a user.
    $user = User::create([
      'name' => 'test_user',
      'mail' => 'test@example.com',
    ]);
    $user->save();

    // Create a mock route match.
    $route = new Route('/user/{user}');
    $route_match = new RouteMatch(
      'entity.user.canonical',
      $route,
      ['user' => $user],
      ['user' => $user]
    );
    $this->container->set('current_route_match', $route_match);

    /** @var \Drupal\visitors\Plugin\VisitorsEvent\Entity $plugin */
    $plugin = $this->eventPluginManager->createInstance('entity');

    // Test viewing a user.
    $context = [
      'route' => 'entity.user.canonical',
      'path' => '/user/' . $user->id(),
    ];
    $result = $plugin->process($context);

    $this->assertNotNull($result);
    $this->assertEquals('canonical', $result['event']);
    $this->assertEquals($user->id(), $result['variables']['plugin_int_1']);
    $this->assertEquals('user', $result['variables']['plugin_var_1']);
    $this->assertEquals('user', $result['variables']['plugin_var_2']);
  }

  /**
   * Tests processing a taxonomy term entity.
   *
   * @covers ::process
   */
  public function testProcessWithRealTaxonomyTerm(): void {
    // Create a term.
    $term = Term::create([
      'vid' => 'tags',
      'name' => 'Test tag',
    ]);
    $term->save();

    // Create a mock route match.
    $route = new Route('/taxonomy/term/{taxonomy_term}');
    $route_match = new RouteMatch(
      'entity.taxonomy_term.canonical',
      $route,
      ['taxonomy_term' => $term],
      ['taxonomy_term' => $term]
    );
    $this->container->set('current_route_match', $route_match);

    /** @var \Drupal\visitors\Plugin\VisitorsEvent\Entity $plugin */
    $plugin = $this->eventPluginManager->createInstance('entity');

    // Test viewing a taxonomy term.
    $context = [
      'route' => 'entity.taxonomy_term.canonical',
      'path' => '/taxonomy/term/' . $term->id(),
    ];
    $result = $plugin->process($context);

    $this->assertNotNull($result);
    $this->assertEquals('canonical', $result['event']);
    $this->assertEquals('taxonomy_term', $result['variables']['plugin_var_1']);
    $this->assertEquals('tags', $result['variables']['plugin_var_2']);
    $this->assertEquals($term->id(), $result['variables']['plugin_int_1']);
  }

  /**
   * Tests processing an entity form.
   *
   * @covers ::process
   */
  public function testProcessWithEntityForm(): void {
    // Create a node.
    $node = Node::create([
      'type' => 'article',
      'title' => 'Test article',
    ]);
    $node->save();

    // Create a mock route match.
    $route = new Route('/node/{node}/edit');
    $route_match = new RouteMatch(
      'entity.node.edit_form',
      $route,
      ['node' => $node],
      ['node' => $node]
    );
    $this->container->set('current_route_match', $route_match);

    /** @var \Drupal\visitors\Plugin\VisitorsEvent\Entity $plugin */
    $plugin = $this->eventPluginManager->createInstance('entity');

    // Simulate a form being processed.
    $form_state = new FormState();
    $form_id = 'node_article_edit_form';
    $form = [];

    // Set the form info in drupal_static.
    $forms = &drupal_static('visitors_form_alter', []);
    $forms[$form_id] = 'node_form';

    // Test editing a node.
    $context = [
      'route' => 'entity.node.edit_form',
      'path' => '/node/' . $node->id() . '/edit',
    ];
    $result = $plugin->process($context);

    $this->assertNotNull($result);
    $this->assertEquals('edit_form', $result['event']);
    $this->assertEquals('node', $result['variables']['plugin_var_1']);
    $this->assertEquals('article', $result['variables']['plugin_var_2']);
    $this->assertEquals($node->id(), $result['variables']['plugin_int_1']);
    $this->assertEquals('node_form', $result['variables']['plugin_var_3']);
    $this->assertEquals($form_id, $result['variables']['plugin_var_4']);
  }

  /**
   * Tests processing with multiple forms.
   *
   * @covers ::process
   */
  public function testProcessWithMultipleForms(): void {
    // Create a node.
    $node = Node::create([
      'type' => 'article',
      'title' => 'Test article',
    ]);
    $node->save();

    // Create a mock route match.
    $route = new Route('/node/{node}/edit');
    $route_match = new RouteMatch(
      'entity.node.edit_form',
      $route,
      ['node' => $node],
      ['node' => $node]
    );
    $this->container->set('current_route_match', $route_match);

    /** @var \Drupal\visitors\Plugin\VisitorsEvent\Entity $plugin */
    $plugin = $this->eventPluginManager->createInstance('entity');

    // Simulate multiple forms being processed.
    $forms = &drupal_static('visitors_form_alter', []);
    $forms['node_article_edit_form'] = 'node_form';
    $forms['another_form'] = 'other_form';

    // Test editing a node.
    $context = [
      'route' => 'entity.node.edit_form',
      'path' => '/node/' . $node->id() . '/edit',
    ];
    $result = $plugin->process($context);

    $this->assertNotNull($result);
    $this->assertEquals('edit_form', $result['event']);
    $this->assertEquals('node', $result['variables']['plugin_var_1']);
    $this->assertEquals('article', $result['variables']['plugin_var_2']);
    $this->assertEquals($node->id(), $result['variables']['plugin_int_1']);
    // Form variables should not be set when multiple forms are present.
    $this->assertArrayNotHasKey('plugin_var_3', $result['variables']);
    $this->assertArrayNotHasKey('plugin_var_4', $result['variables']);
  }

}
