<?php

namespace Drupal\acquia_contenthub_subscriber\EventSubscriber\EntityDataTamper;

use Acquia\ContentHubClient\CDF\CDFObject;
use Drupal\acquia_contenthub\AcquiaContentHubEvents;
use Drupal\acquia_contenthub\Event\EntityDataTamperEvent;
use Drupal\acquia_contenthub_subscriber\Libs\UserSyndicationSettingsInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\depcalc\DependentEntityWrapper;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Skip user and user role entities from syndication.
 *
 * Replaces user with the configured proxy user if conditions are met.
 */
class SkipUserAndUserRoleSyndication implements EventSubscriberInterface {

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

  /**
   * Entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $etm;

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

  /**
   * The entity repository service.
   *
   * @var \Drupal\Core\Entity\EntityRepositoryInterface
   */
  protected EntityRepositoryInterface $entityRepository;

  /**
   * SkipUserAndUserRoleSyndication constructor.
   *
   * @param \Drupal\acquia_contenthub_subscriber\Libs\UserSyndicationSettingsInterface $user_settings
   *   User syndication settings.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $etm
   *   Entity type manager.
   * @param \Psr\Log\LoggerInterface $logger_channel
   *   Logger.
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
   *   The entity repository service.
   */
  public function __construct(UserSyndicationSettingsInterface $user_settings, EntityTypeManagerInterface $etm, LoggerInterface $logger_channel, EntityRepositoryInterface $entity_repository) {
    $this->userSettings = $user_settings;
    $this->etm = $etm;
    $this->logger = $logger_channel;
    $this->entityRepository = $entity_repository;
  }

  /**
   * {@inheritDoc}
   */
  public static function getSubscribedEvents(): array {
    $events[AcquiaContentHubEvents::ENTITY_DATA_TAMPER][] = 'onDataTamper';
    return $events;
  }

  /**
   * Tampers user and user role entity from CDF.
   *
   * @param \Drupal\acquia_contenthub\Event\EntityDataTamperEvent $event
   *   Entity data tamper event.
   *
   * @throws \Exception
   */
  public function onDataTamper(EntityDataTamperEvent $event): void {
    if (!$this->userSettings->isUserSyndicationDisabled() && !$this->userSettings->isUserRoleSyndicationDisabled()) {
      return;
    }
    $cdf = $event->getCdf();
    foreach ($cdf->getEntities() as $object) {
      if ($event->getStack()->hasDependency($object->getUuid())) {
        continue;
      }
      if ($this->userSettings->isUserSyndicationDisabled() && $this->isUserEntity($object)) {
        $this->tamperUserEntity($event, $object);
        continue;
      }
      if ($this->userSettings->isUserRoleSyndicationDisabled() && $this->isUserRoleEntity($object)) {
        $this->tamperUserRoleEntity($event, $object);
      }
    }
  }

  /**
   * Tampers CDF to load existing user role or default-authenticated role.
   *
   * @param \Drupal\acquia_contenthub\Event\EntityDataTamperEvent $event
   *   Entity data tamper event.
   * @param \Acquia\ContentHubClient\CDF\CDFObject $object
   *   Cdf object.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  protected function tamperUserRoleEntity(EntityDataTamperEvent $event, CDFObject $object): void {
    $data = Yaml::decode(base64_decode($object->getMetadata()['data']));
    foreach ($data as $user_role) {
      $entity = !empty($user_role['id']) ? $this->etm->getStorage('user_role')->load($user_role['id']) : '';
      if (empty($entity)) {
        $entity = $this->etm->getStorage('user_role')->load('authenticated');
      }
      $this->addEntityToStack($event, $entity, $object->getUuid());
    }
  }

  /**
   * Tampers CDF object to load existing user or proxy user.
   *
   * @param \Drupal\acquia_contenthub\Event\EntityDataTamperEvent $event
   *   Entity data tamper event.
   * @param \Acquia\ContentHubClient\CDF\CDFObject $object
   *   Cdf object.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function tamperUserEntity(EntityDataTamperEvent $event, CDFObject $object): void {
    $user = $this->entityRepository->loadEntityByUuid('user', $object->getUuid());

    if (!$user) {
      $this->logger->info('Could not load user with uuid, trying to load user by mail. Having uuid: {uuid}', ['uuid' => $object->getUuid()]);
      $user = $this->getUserByMail($object);
    }

    if (!$user) {
      $user = $this->getUserByName($object);
      if ($user) {
        $this->logger->info('Could not load user with mail, hence loaded by name. Having uuid: {uuid}', ['uuid' => $object->getUuid()]);
      }
    }

    if (!$user) {
      $user = $this->userSettings->getProxyUser();
      if ($user === NULL) {
        return;
      }
    }

    $this->addEntityToStack($event, $user, $object->getUuid());
  }

  /**
   * Loads user account by mail.
   *
   * @param \Acquia\ContentHubClient\CDF\CDFObject $cdf
   *   Cdf object.
   *
   * @return \Drupal\user\UserInterface|false
   *   User account.
   */
  protected function getUserByMail(CDFObject $cdf) {
    $mail_attribute = $cdf->getAttribute('mail');
    if (!$mail_attribute) {
      return FALSE;
    }
    $mail = $mail_attribute->getValue()[CDFObject::LANGUAGE_UNDETERMINED];
    return user_load_by_mail($mail);
  }

  /**
   * Loads user account by name.
   *
   * @param \Acquia\ContentHubClient\CDF\CDFObject $cdf
   *   Cdf object.
   *
   * @return \Drupal\user\UserInterface|false
   *   User account.
   */
  protected function getUserByName(CDFObject $cdf) {
    $name_attribute = $cdf->getAttribute('username');
    if (!$name_attribute) {
      return FALSE;
    }
    $name = $name_attribute->getValue()[CDFObject::LANGUAGE_UNDETERMINED];
    return user_load_by_name($name);
  }

  /**
   * Adds entity to stack.
   *
   * @param \Drupal\acquia_contenthub\Event\EntityDataTamperEvent $event
   *   Tamper event.
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   Entity object.
   * @param string $uuid
   *   Uuid.
   *
   * @throws \Exception
   */
  protected function addEntityToStack(EntityDataTamperEvent $event, EntityInterface $entity, string $uuid): void {
    $wrapper = new DependentEntityWrapper($entity);
    $wrapper->setRemoteUuid($uuid);
    $event->getStack()->addDependency($wrapper, FALSE);
  }

  /**
   * Checks whether this cdf object is a user entity.
   *
   * @param \Acquia\ContentHubClient\CDF\CDFObject $object
   *   Cdf object.
   *
   * @return bool
   *   True if it's a user entity else false.
   */
  protected function isUserEntity(CDFObject $object): bool {
    return $object->getType() === 'drupal8_content_entity' &&
      $object->getAttribute('entity_type')->getValue()[LanguageInterface::LANGCODE_NOT_SPECIFIED] === 'user';
  }

  /**
   * Checks whether this cdf object is a user role entity.
   *
   * @param \Acquia\ContentHubClient\CDF\CDFObject $object
   *   Cdf object.
   *
   * @return bool
   *   True if it's a user role entity else false.
   */
  protected function isUserRoleEntity(CDFObject $object): bool {
    return $object->getType() === 'drupal8_config_entity' &&
      $object->getAttribute('entity_type')->getValue()[LanguageInterface::LANGCODE_NOT_SPECIFIED] === 'user_role';
  }

}
