<?php

namespace Drupal\acquia_contenthub_publisher;

use Drupal\acquia_contenthub\AcquiaContentHubEntityTrackerInterface;
use Drupal\acquia_contenthub\AcquiaContentHubRequeueBase;
use Drupal\Component\Uuid\Uuid;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\depcalc\Cache\DepcalcCacheBackend;
use Psr\Log\LoggerInterface;

/**
 * The PublisherRequeuer re-enqueues entities to export.
 */
class PublisherRequeuer extends AcquiaContentHubRequeueBase {

  /**
   * The Content Hub Entity Enqueuer.
   *
   * @var \Drupal\acquia_contenthub_publisher\ContentHubEntityEnqueuer
   */
  protected $entityEnqueuer;

  /**
   * The entity type bundle info.
   *
   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
   */
  protected $entityTypeBundleInfo;

  /**
   * The PublisherRequeuer constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
   *   The entity type bundle information instance.
   * @param \Drupal\acquia_contenthub\AcquiaContentHubEntityTrackerInterface $publisher_tracker
   *   The entity tracker.
   * @param \Drupal\depcalc\Cache\DepcalcCacheBackend $depcalc_cache
   *   The depcalc cache instance.
   * @param \Drupal\acquia_contenthub_publisher\ContentHubEntityEnqueuer $entity_enqueuer
   *   The contenthub entity enqueuer.
   * @param \Psr\Log\LoggerInterface $logger_channel
   *   The logger channel.
   */
  public function __construct(EntityTypeManagerInterface $entityTypeManager, EntityTypeBundleInfoInterface $entity_type_bundle_info, AcquiaContentHubEntityTrackerInterface $publisher_tracker, DepcalcCacheBackend $depcalc_cache, ContentHubEntityEnqueuer $entity_enqueuer, LoggerInterface $logger_channel) {
    parent::__construct($depcalc_cache, $logger_channel, $entityTypeManager, $publisher_tracker);

    $this->entityEnqueuer = $entity_enqueuer;
    $this->entityTypeBundleInfo = $entity_type_bundle_info;
  }

  /**
   * {@inheritDoc}
   */
  public function reQueue(string $entity_type, string $bundle, string $uuid, bool $only_queued_entities, bool $use_tracking_table, string $reason = 'manual', string $action = 'entity_create', bool $service = FALSE): array {
    $this->validateParameters(
      $entity_type,
      $bundle,
      $uuid,
      $only_queued_entities,
      $use_tracking_table
    );

    if ($only_queued_entities || $use_tracking_table) {
      if ($only_queued_entities) {
        // We are going to re-queue all the queued entities.
        $entities = $this->entityTracker->listTrackedEntities(AcquiaContentHubEntityTrackerInterface::QUEUED);
      }
      else {
        // We are going to re-queue ALL entities
        // (if given options of a particular entity type and bundle).
        $entities = $this->entityTracker->listTrackedEntities([
          PublisherTracker::EXPORTED,
          PublisherTracker::CONFIRMED,
          AcquiaContentHubEntityTrackerInterface::QUEUED,
        ], $entity_type
        );
      }
      $this->clearCacheAndNullifyHashes(TRUE);
      $this->enqueueEntities($entities, $entity_type, $bundle);
      return [];
    }

    if (empty($entity_type)) {
      throw new \Exception('Please specify entity type.');
    }

    $storage = $this->entityTypeManager->getStorage($entity_type);
    if ($uuid) {
      $this->enqueueByUuid($storage, $uuid);
    }
    else {
      $bundle_info = !empty($bundle) ? $this->getBundleKeyAndInfo($entity_type, $bundle) : [];
      $this->enqueueWithBundleData($entity_type, $storage, $bundle, $bundle_info);
    }
    return [];
  }

  /**
   * Requeue entities with bundle data.
   *
   * @param string $entity_type
   *   The entity type.
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   A storage instance.
   * @param string $bundle
   *   The entity bundle.
   * @param array $data
   *   The data array.
   *
   * @throws \Exception
   */
  public function enqueueWithBundleData(string $entity_type, EntityStorageInterface $storage, string $bundle = '', array $data = []): void {
    // We are going to re-queue ALL entities that match a particular entity
    // type (and bundle).
    $entities = $storage->loadByProperties($data);
    if (empty($entities)) {
      throw new \Exception(sprintf('No entities found for type %s.', $entity_type));
    }

    $this->clearCacheAndNullifyHashes(TRUE);
    $this->entityEnqueuer->enqueueEntities('update', ...$entities);

    $msg = !empty($bundle) ? "and bundle = \"{$bundle}\"." : '';
    $this->logger->info('Processed {count} entities of type = {entity_type} {msg} for export.', [
      'count' => count($entities),
      'entity_type' => $entity_type,
      'msg' => $msg,
    ]);
  }

  /**
   * Enqueues by UUID.
   *
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage.
   * @param string $uuid
   *   The UUID.
   *
   * @throws \Exception
   */
  protected function enqueueByUuid(EntityStorageInterface $storage, string $uuid): void {
    if (!Uuid::isValid($uuid)) {
      throw new \Exception('Invalid UUID.');
    }

    $entity = $storage->loadByProperties(['uuid' => $uuid]);
    $entity = reset($entity);

    if (!$entity instanceof EntityInterface) {
      throw new \Exception(sprintf('Entity with UUID %s not found.', $uuid));
    }
    $this->clearCacheAndNullifyHashes(TRUE);
    $this->entityEnqueuer->enqueueEntities('update', $entity);

    $this->logger->info('Queued entity with UUID {uuid}.', [
      'uuid' => $uuid,
    ]);
  }

  /**
   * {@inheritDoc}
   */
  protected function enqueueEntities(array $entities, ?string $entity_type, ?string $bundle): void {
    $entities_to_queue = [];
    foreach ($entities as $queued_entity) {
      if (!$this->validEntity($queued_entity, $entity_type, $bundle)) {
        continue;
      }

      $entity_type_id = !empty($entity_type) ? $entity_type : $queued_entity['entity_type'];
      $entities_to_queue[] = $this->entityTypeManager->getStorage($entity_type_id)->load($queued_entity['entity_id']);
    }
    $this->entityEnqueuer->enqueueEntities('update', ...$entities_to_queue);
    $this->logger->info('Processed {count} entities for export.', [
      'count' => count($entities_to_queue),
    ]);
  }

  /**
   * Provides entity bundle key and info.
   *
   * @param string $entity_type
   *   The entity type.
   * @param string $bundle
   *   The entity bundle.
   *
   * @return array
   *   Array containing bundle key and bundle info.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Exception
   */
  protected function getBundleKeyAndInfo(string $entity_type, string $bundle): array {
    $bundle_key = $this->entityTypeManager->getDefinition($entity_type)->getKey('bundle');
    if (empty($bundle_key)) {
      throw new \Exception(sprintf('The "%s" entity type does not support bundles.', $entity_type));
    }

    $bundle_info = $this->entityTypeBundleInfo->getBundleInfo($entity_type);
    if (!in_array($bundle, array_keys($bundle_info))) {
      throw new \Exception(sprintf('The bundle "%s" does not exist.', $bundle));
    }

    return [
      $bundle_key => $bundle,
    ];
  }

}
