<?php

namespace Drupal\acquia_contenthub_subscriber\EventSubscriber\ParseCdf;

use Drupal\acquia_contenthub\AcquiaContentHubEntityTrackerInterface;
use Drupal\acquia_contenthub\AcquiaContentHubEvents;
use Drupal\acquia_contenthub\Event\ParseCdfEntityEvent;
use Drupal\acquia_contenthub\Event\Queue\QueueItemProcessFinishedEvent;
use Drupal\acquia_contenthub\Exception\ContentHubFileException;
use Drupal\acquia_contenthub\Plugin\FileSchemeHandler\FileSchemeHandlerManagerInterface;
use Drupal\acquia_contenthub_subscriber\ContentHubImportQueue;
use Drupal\acquia_contenthub_subscriber\SubscriberTracker;
use Drupal\Core\Language\LanguageInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Handles CDF parsing of files.
 */
class File implements EventSubscriberInterface {

  /**
   * The file scheme handler manager.
   *
   * @var \Drupal\acquia_contenthub\Plugin\FileSchemeHandler\FileSchemeHandlerManagerInterface
   */
  protected $manager;

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

  /**
   * Entity tracker service for subscriber.
   *
   * @var \Drupal\acquia_contenthub\AcquiaContentHubEntityTrackerInterface
   */
  protected $tracker;

  /**
   * The list of unsaved files.
   *
   * These files were unsaved, due to some error during the parsing
   * process.
   *
   * @var array
   */
  private $unsavedFiles = [];

  /**
   * FileEntityHandler constructor.
   *
   * @param \Drupal\acquia_contenthub\Plugin\FileSchemeHandler\FileSchemeHandlerManagerInterface $manager
   *   File scheme handler manager.
   * @param \Drupal\acquia_contenthub\AcquiaContentHubEntityTrackerInterface $tracker
   *   Entity tracker service for subscriber.
   * @param \Psr\Log\LoggerInterface $logger
   *   The acquia_contenthub_subscriber logger channel.
   */
  public function __construct(FileSchemeHandlerManagerInterface $manager, AcquiaContentHubEntityTrackerInterface $tracker, LoggerInterface $logger) {
    $this->manager = $manager;
    $this->logger = $logger;
    $this->tracker = $tracker;
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    $events[AcquiaContentHubEvents::PARSE_CDF][] = ['onParseCdf', 110];
    $events[AcquiaContentHubEvents::QUEUE_ITEM_PROCESS_FINISHED][] = ['onQueueFinished'];
    return $events;
  }

  /**
   * Parse CDF attributes to import files as necessary.
   *
   * @param \Drupal\acquia_contenthub\Event\ParseCdfEntityEvent $event
   *   The Parse CDF Entity Event.
   *
   * @throws \Drupal\Component\Plugin\Exception\PluginException
   */
  public function onParseCdf(ParseCdfEntityEvent $event): void {
    $cdf = $event->getCdf();
    $entity_type = $cdf->getAttribute('entity_type');

    if ($entity_type && $entity_type->getValue()[LanguageInterface::LANGCODE_NOT_SPECIFIED] !== 'file') {
      return;
    }

    if ($cdf->getAttribute('file_scheme')) {
      $scheme = $cdf->getAttribute('file_scheme')->getValue()[LanguageInterface::LANGCODE_NOT_SPECIFIED];
      /** @var \Drupal\acquia_contenthub\Plugin\FileSchemeHandler\FileSchemeHandlerInterface $handler */
      $handler = $this->manager->createInstance($scheme);
      try {
        $handler->getFile($cdf);
      }
      catch (ContentHubFileException $e) {
        $url = $e->getResourceUri();
        $this->logger->error('The file ({uuid} | {url}) cannot be saved: {error} | code: {code}', [
          'url' => $url,
          'uuid' => $cdf->getUuid(),
          'error' => $e->getMessage(),
          'code' => $e->getCode(),
        ]);
        $this->unsavedFiles[] = $cdf->getUuid();
      }
      catch (\Exception $e) {
        $this->logger->error('Error during file save ({uuid}): {error}', [
          'uuid' => $cdf->getUuid(),
          'error' => $e->getMessage(),
        ]);
        $this->unsavedFiles[] = $cdf->getUuid();
      }

      return;
    }

    $label_attribute = $cdf->getAttribute('label');
    $label = $label_attribute ? $label_attribute->getValue()[LanguageInterface::LANGCODE_NOT_SPECIFIED] : '';

    $this->logger->warning('File {label} does not have a scheme and therefore was not copied. This likely 
    means that its scheme is remote, unsupported or that it should already exist within a module, theme or library in 
    the subscriber.', ['label' => $label]);
  }

  /**
   * Resets file entities in tracking table if they failed to be saved.
   *
   * In order to have a more seamless experience, we nullify the hashes of
   * items that could not be saved due to some error. This makes it possible
   * to re-fetch them later. Since entities only get into the tracking table
   * at a later time, this task is executed at the end of the process.
   *
   * @param \Drupal\acquia_contenthub\Event\Queue\QueueItemProcessFinishedEvent $event
   *   The event this function subscribed to.
   */
  public function onQueueFinished(QueueItemProcessFinishedEvent $event): void {
    if ($event->getQueueName() !== ContentHubImportQueue::QUEUE_NAME || empty($this->unsavedFiles)) {
      return;
    }

    $this->tracker->nullifyHashes([
      AcquiaContentHubEntityTrackerInterface::QUEUED,
      SubscriberTracker::IMPORTED,
    ], ['file'], $this->unsavedFiles);

    $this->logger->warning(
      'Some files could not be saved during the queue run, therefore they were deleted from subscriber tracking table: {uuid}. Queue items: {items}',
      [
        'uuid' => implode(',', $this->unsavedFiles),
        'items' => implode(',', $event->getItemContent()),
      ]
    );

    // Reset after every processed queue item.
    $this->unsavedFiles = [];
  }

}
