<?php

namespace Drupal\Tests\acquia_contenthub_subscriber\Kernel\EventSubscriber\EntityDataTamper;

use Acquia\ContentHubClient\CDF\CDFObject;
use Acquia\ContentHubClient\CDFAttribute;
use Acquia\ContentHubClient\CDFDocument;
use Drupal\acquia_contenthub\Event\EntityDataTamperEvent;
use Drupal\acquia_contenthub_subscriber\EventSubscriber\EntityDataTamper\SkipUserAndUserRoleSyndication;
use Drupal\acquia_contenthub_subscriber\Libs\UserSyndicationSettingsInterface;
use Drupal\depcalc\DependencyStack;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\acquia_contenthub\Kernel\Traits\CdfDocumentCreatorTrait;
use Drupal\Tests\acquia_contenthub\Unit\Helpers\LoggerMock;
use Drupal\user\UserInterface;
use Psr\Log\LoggerInterface;

/**
 * Tests user entity tampers.
 *
 * @coversDefaultClass \Drupal\acquia_contenthub_subscriber\EventSubscriber\EntityDataTamper\SkipUserAndUserRoleSyndication
 *
 * @requires module depcalc
 *
 * @group acquia_contenthub_subscriber
 */
class SkipUserSyndicationTest extends EntityKernelTestBase {

  use CdfDocumentCreatorTrait;

  protected const ORIGINAL_USER_UUID = '995f955b-08a9-4436-a0c7-1cde093ee174';

  /**
   * User settings.
   *
   * @var \Drupal\acquia_contenthub_subscriber\Libs\UserSyndicationSettingsInterface
   */
  protected UserSyndicationSettingsInterface $userSettings;

  /**
   * SUT.
   *
   * @var \Drupal\acquia_contenthub_subscriber\EventSubscriber\EntityDataTamper\SkipUserAndUserRoleSyndication
   */
  protected SkipUserAndUserRoleSyndication $sut;

  /**
   * Proxy user.
   *
   * @var \Drupal\user\UserInterface
   */
  protected UserInterface $proxyUser;

  /**
   * Cdf document.
   *
   * @var \Acquia\ContentHubClient\CDFDocument
   */
  protected CDFDocument $cdf;

  /**
   * Logger mock.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected LoggerInterface $logger;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'acquia_contenthub_subscriber',
    'acquia_contenthub',
    'depcalc',
  ];

  /**
   * {@inheritDoc}
   */
  public function setUp(): void {
    parent::setUp();
    $this->userSettings = $this->container->get('acquia_contenthub_subscriber.user_syndication.settings');
    $this->proxyUser = $this->createUser();
    $this->logger = new LoggerMock();
    $this->sut = new SkipUserAndUserRoleSyndication($this->userSettings, $this->container->get('entity_type.manager'), $this->logger, $this->container->get('entity.repository'));
    $this->cdf = $this->createCdfDocumentFromFixtureFile('node/node_page.json');
  }

  /**
   * Tests that original user is replaced with proxy user.
   */
  public function testCdfTamperWithProxyUser(): void {
    $this->userSettings->toggleUserSyndication(TRUE);
    $this->userSettings->setProxyUser($this->proxyUser->id());
    $stack = $this->tamperCdf();
    $original_user = $this->cdf->getCdfEntity(self::ORIGINAL_USER_UUID);
    $this->assertTrue($stack->hasDependency($original_user->getUuid()), 'Original user is added to the stack as processed entity.');
    $this->assertEquals($this->proxyUser->uuid(), $stack->getDependency($original_user->getUuid())->getUuid(), 'Original user was replaced with proxy user.');
  }

  /**
   * Tests that existing user is added to stack.
   */
  public function testCdfTamperWithExistingUser(): void {
    $this->userSettings->toggleUserSyndication(TRUE);
    // This should have no effect
    // when original user already exists on subscriber.
    $this->userSettings->setProxyUser($this->proxyUser->id());
    $email = 'admin@example.com';
    // Create original user.
    if (version_compare(\Drupal::VERSION, '10.1.0', '>=')) {
      $this->createUser([], 'admin', TRUE, [
        'mail' => $email,
      ]);
    }
    else {
      $this->createUser([
        'mail' => $email,
      ]);
    }
    $stack = $this->tamperCdf();
    $original_user = $this->cdf->getCdfEntity(self::ORIGINAL_USER_UUID);
    $this->assertTrue($stack->hasDependency($original_user->getUuid()), 'Original user is added to the stack as processed entity.');
    /** @var \Drupal\user\UserInterface $existing_user */
    $existing_user = $stack->getDependency($original_user->getUuid())->getEntity();
    $this->assertEquals($email, $existing_user->getEmail(), 'Existing user was loaded with same email address.');
  }

  /**
   * Tests that nothing happens if user syndication is disabled.
   *
   * And no proxy user is set.
   */
  public function testCdfTamperWithNoProxyUser(): void {
    $this->userSettings->toggleUserSyndication(TRUE);
    $stack = $this->tamperCdf();
    $original_user = $this->cdf->getCdfEntity(self::ORIGINAL_USER_UUID);
    $this->assertFalse($stack->hasDependency($original_user->getUuid()));
    $this->assertFalse($stack->hasDependency($this->proxyUser->uuid()));
    $this->assertEmpty($stack->getDependencies());
  }

  /**
   * Tests that existing user with no mail is added to stack.
   */
  public function testCdfTamperWithExistingUserWithNoMail(): void {
    $this->userSettings->toggleUserSyndication(TRUE);
    if (version_compare(\Drupal::VERSION, '10.1.0', '>=')) {
      $user = $this->createUser([], 'test_user');
    }
    else {
      $user = $this->createUser([
        'name' => 'test_user',
      ]);
    }
    $cdf = $this->initialiseCdf($user);
    $event = new EntityDataTamperEvent($cdf, new DependencyStack());
    $this->sut->onDataTamper($event);
    $stack = $event->getStack();

    $this->assertTrue($stack->hasDependency($user->uuid()));
    $entity_wrapper = $stack->getDependenciesByUuid([$user->uuid()]);
    $this->assertNotEmpty($entity_wrapper);
    /** @var \Drupal\user\UserInterface $loaded_user */
    $loaded_user = $entity_wrapper[$user->uuid()]->getEntity();
    $this->assertSame($user->uuid(), $loaded_user->uuid(), '');
    $this->assertSame($user->getAccountName(), $loaded_user->getAccountName());
  }

  /**
   * Tests that existing user with updated details is added to stack.
   *
   * But changes don't reflect.
   */
  public function testCdfTamperWithUpdatedUserDetails(): void {
    $this->userSettings->toggleUserSyndication(TRUE);
    $email = 'admin@example.com';
    // Create original user.
    if (version_compare(\Drupal::VERSION, '10.1.0', '>=')) {
      $user = $this->createUser([], 'admin', TRUE, [
        'mail' => $email,
        'name' => 'admin',
      ]);
    }
    else {
      $user = $this->createUser([
        'mail' => $email,
        'name' => 'admin',
      ]);
    }
    $cdf = $this->initialiseCdf($user);
    $event = new EntityDataTamperEvent($cdf, new DependencyStack());
    $this->sut->onDataTamper($event);
    $stack = $event->getStack();

    $this->assertTrue($stack->hasDependency($user->uuid()));
    $entity_wrapper = $stack->getDependenciesByUuid([$user->uuid()]);
    $this->assertNotEmpty($entity_wrapper);
    /** @var \Drupal\user\UserInterface $loaded_user */
    $loaded_user = $entity_wrapper[$user->uuid()]->getEntity();
    $this->assertSame($user->uuid(), $loaded_user->uuid(), '');
    $this->assertSame('admin@example.com', $loaded_user->getEmail());
    $this->assertSame('admin', $loaded_user->getAccountName());

    // Updating user details to create new CDF.
    $user->set('mail', 'admin1@example.com')->save();
    $user->set('name', 'admin1')->save();
    $new_cdf = $this->initialiseCdf($user);
    // Changing back the user details.
    $user->set('mail', 'admin@example.com')->save();
    $user->set('name', 'admin')->save();
    $event = new EntityDataTamperEvent($new_cdf, new DependencyStack());
    $this->sut->onDataTamper($event);
    $stack = $event->getStack();

    $this->assertTrue($stack->hasDependency($user->uuid()));
    $entity_wrapper = $stack->getDependenciesByUuid([$user->uuid()]);
    $this->assertNotEmpty($entity_wrapper);
    /** @var \Drupal\user\UserInterface $loaded_user */
    $loaded_user = $entity_wrapper[$user->uuid()]->getEntity();
    $this->assertSame('admin@example.com', $loaded_user->getEmail());
    $this->assertSame('admin', $loaded_user->getAccountName());
  }

  /**
   * Initialises Cdf document with user.
   *
   * @param \Drupal\user\UserInterface $user
   *   User entity.
   *
   * @return \Acquia\ContentHubClient\CDFDocument
   *   Cdf document with user.
   */
  protected function initialiseCdf(UserInterface $user): CDFDocument {
    $type_cdf_attribute = $this->prophesize(CDFAttribute::class);
    $type_cdf_attribute->getValue()->willReturn([
      CDFObject::LANGUAGE_UNDETERMINED => 'user',
    ]);
    $cdfObject = $this->prophesize(CDFObject::class);
    $cdfObject->getType()->willReturn('drupal8_content_entity');
    $cdfObject->getAttribute('entity_type')->willReturn($type_cdf_attribute->reveal());
    $cdfObject->getAttribute('is_anonymous')->willReturn(NULL);
    $cdfObject->getAttribute('mail')->willReturn(NULL);
    $name_cdf_attribute = $this->prophesize(CDFAttribute::class);
    $name_cdf_attribute->getValue()->willReturn([
      CDFObject::LANGUAGE_UNDETERMINED => $user->getAccountName(),
    ]);
    $cdfObject->getAttribute('username')->willReturn($name_cdf_attribute->reveal());
    $cdfObject->getUuid()->willReturn($user->uuid());

    return new CDFDocument($cdfObject->reveal());
  }

  /**
   * Tests that nothing happens if user syndication is enabled.
   *
   * Even if proxy user is set.
   *
   * @throws \Exception
   */
  public function testCdfTamperWithUserSyndicationEnabled(): void {
    $this->userSettings->setProxyUser($this->proxyUser->id());
    $stack = $this->tamperCdf();
    $original_user = $this->cdf->getCdfEntity(self::ORIGINAL_USER_UUID);
    $this->assertFalse($stack->hasDependency($original_user->getUuid()));
    $this->assertFalse($stack->hasDependency($this->proxyUser->uuid()));
    $this->assertEmpty($stack->getDependencies());
  }

  /**
   * Tampers cdf document with data tamper event subscriber.
   *
   * @return \Drupal\depcalc\DependencyStack
   *   Dependency stack.
   *
   * @throws \Exception
   */
  protected function tamperCdf(): DependencyStack {
    $event = new EntityDataTamperEvent($this->cdf, new DependencyStack());
    $this->sut->onDataTamper($event);
    return $event->getStack();
  }

}
