<?php

namespace Drupal\acquia_contenthub_subscriber\EventSubscriber\LoadLocalEntity;

use Acquia\ContentHubClient\CDF\CDFObject;
use Drupal\acquia_contenthub\AcquiaContentHubEvents;
use Drupal\acquia_contenthub\Event\LoadLocalEntityEvent;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\path_alias\PathAliasInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Class LoadMatchingPathAlias.
 *
 * Loads a local path alias entity by its information.
 *
 * @package Drupal\acquia_contenthub\EventSubscriber\LoadLocalEntity
 */
class LoadMatchingPathAlias implements EventSubscriberInterface {

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

  /**
   * Import settings configs.
   *
   * @var \Drupal\Core\Config\ImmutableConfig
   */
  private ImmutableConfig $importSettings;

  /**
   * The acquia_contenthub_subscriber logger channel.
   *
   * @var \Psr\Log\LoggerInterface
   */
  private LoggerInterface $logger;

  /**
   * Constructor for LoadMatchingPathAlias.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   Config factory.
   * @param \Psr\Log\LoggerInterface $logger
   *   The acquia_contenthub_subscriber channel.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, LoggerInterface $logger) {
    $this->entityTypeManager = $entity_type_manager;
    $this->importSettings = $config_factory->get('acquia_contenthub_subscriber.import_settings');
    $this->logger = $logger;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    $events[AcquiaContentHubEvents::LOAD_LOCAL_ENTITY][] =
      ['onLoadLocalEntity', 7];
    return $events;
  }

  /**
   * Loads local matching path alias to prevent duplicacy.
   *
   * @param \Drupal\acquia_contenthub\Event\LoadLocalEntityEvent $event
   *   The local entity loading event.
   *
   * @throws \Exception
   */
  public function onLoadLocalEntity(LoadLocalEntityEvent $event): void {
    $cdf = $event->getCdf();
    if (!$this->isPathAlias($cdf) ||
      $this->importSettings->get('disable_duplicate_path_aliases') !== TRUE
    ) {
      return;
    }
    $data = json_decode(base64_decode($cdf->getMetadata()['data']), TRUE);
    $langcode = $cdf->getMetadata()['default_language'];
    $alias = $data['alias'];

    $path_alias = $this->getExistingPathAlias($alias, $langcode);
    if ($path_alias) {
      $event->setEntity($path_alias);
      $this->logger->notice(sprintf('Overriding local path alias: %s with pid: %s', $path_alias->getAlias(), $path_alias->id()));
      $event->stopPropagation();
    }
  }

  /**
   * Checks if object is of type path alias.
   *
   * @param \Acquia\ContentHubClient\CDF\CDFObject $cdf_object
   *   CDF Object.
   *
   * @return bool
   *   TRUE if CDF object is path alias.
   */
  protected function isPathAlias(CDFObject $cdf_object): bool {
    $type = $cdf_object->getAttribute('entity_type');

    return $type->getValue()[CDFObject::LANGUAGE_UNDETERMINED] === 'path_alias';
  }

  /**
   * Looks for an existing path alias based on alias and langcode from CDF.
   *
   * @param array $alias
   *   The path alias data from the CDF.
   * @param string $langcode
   *   The langcode.
   *
   * @return \Drupal\path_alias\PathAliasInterface|null
   *   The existing path alias (if any).
   */
  protected function getExistingPathAlias(array $alias, string $langcode): ?PathAliasInterface {
    $alias_value = $alias['value'][$langcode] ?? [];
    if (empty($alias_value)) {
      return NULL;
    }
    /** @var \Drupal\path_alias\PathAliasStorage $path_alias_storage */
    $path_alias_storage = $this->entityTypeManager->getStorage('path_alias');
    $query = $path_alias_storage
      ->getQuery()
      ->accessCheck(FALSE);

    $query->condition('alias', $alias_value, '=');
    $query->condition('langcode', $langcode, '=');
    $path_alias = $query->execute();
    if (empty($path_alias)) {
      return NULL;
    }
    if (count($path_alias) > 1) {
      $this->logger->notice(sprintf('Multiple duplicate local path aliases found for alias: %s with pids: %s.',
        $alias_value,
        implode(',', array_keys($path_alias))
      ));
    }
    // First matching path alias will be used to override.
    $pid = reset($path_alias);
    return $path_alias_storage->load($pid);
  }

}
