<?php

namespace Drupal\acquia_contenthub\Commands;

use Consolidation\AnnotatedCommand\CommandError;
use Drupal\acquia_contenthub\PubSubModuleStatusChecker;
use Drupal\acquia_contenthub\RequeuerInterface;
use Drupal\acquia_contenthub\Settings\ConnectionDetailsInterface;
use Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface;
use Drupal\acquia_contenthub_publisher\PublisherRequeuer;
use Drupal\acquia_contenthub_subscriber\SubscriberRequeuer;
use Drush\Commands\DrushCommands;

/**
 * Drush commands for Acquia Content Hub enqueue entities.
 *
 * @package Drupal\acquia_contenthub\Commands
 */
class AcquiaContentHubEnqueueEntitiesCommands extends DrushCommands {

  /**
   * Import operation.
   */
  protected const OPERATION_IMPORT = 'import';

  /**
   * Export operation.
   */
  protected const OPERATION_EXPORT = 'export';

  /**
   * Exist codes.
   */
  protected const REQUEUER_ERROR = 2;

  /**
   * The publisher subscriber checker.
   *
   * @var \Drupal\acquia_contenthub\PubSubModuleStatusChecker
   */
  protected PubSubModuleStatusChecker $pubSubChecker;

  /**
   * The publisher-requeuer.
   *
   * @var \Drupal\acquia_contenthub_publisher\PublisherRequeuer
   */
  protected PublisherRequeuer $pubRequeuer;

  /**
   * The subscriber-requeuer.
   *
   * @var \Drupal\acquia_contenthub_subscriber\SubscriberRequeuer
   */
  protected SubscriberRequeuer $subRequeuer;

  /**
   * Content Hub configurations.
   *
   * @var \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface
   */
  protected ContentHubConfigurationInterface $achConfigurations;

  /**
   * AcquiaContentHubEnqueueEntitiesByBundleCommands constructor.
   *
   * @param \Drupal\acquia_contenthub\PubSubModuleStatusChecker $pub_sub_checker
   *   The publisher subscriber checker.
   * @param \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ach_configurations
   *   Content Hub configurations.
   */
  public function __construct(PubSubModuleStatusChecker $pub_sub_checker, ContentHubConfigurationInterface $ach_configurations) {
    $this->pubSubChecker = $pub_sub_checker;
    $this->achConfigurations = $ach_configurations;
  }

  /**
   * Sets publisher-requeuer if available.
   *
   * @param \Drupal\acquia_contenthub_publisher\PublisherRequeuer $pub_requeuer
   *   The publisher-requeuer.
   */
  public function setPublisherRequeuer(PublisherRequeuer $pub_requeuer): void {
    $this->pubRequeuer = $pub_requeuer;
  }

  /**
   * Sets subscriber-requeuer if available.
   *
   * @param \Drupal\acquia_contenthub_subscriber\SubscriberRequeuer $sub_requeuer
   *   The subscriber-requeuer.
   */
  public function setSubscriberRequeuer(SubscriberRequeuer $sub_requeuer): void {
    $this->subRequeuer = $sub_requeuer;
  }

  /**
   * Validates method argument.
   *
   * @hook validate acquia:contenthub-re-queue
   *
   * @throws \Exception
   */
  public function validateEnqueueEntities(): ?CommandError {
    $op = $this->input()->getArgument('op');
    if (!$this->isValidOperation($op)) {
      return new CommandError("Invalid operation specified: $op", self::EXIT_FAILURE);
    }

    $service_flag = $this->input()->getOption('service');
    $reason = $this->input()->getOption('reason');
    $action = $this->input()->getOption('action');

    if ($op === self::OPERATION_EXPORT && $service_flag && ($reason || $action)) {
      return new CommandError('Reason, Action and Service flag should be used for import only.', self::EXIT_FAILURE);
    }

    if ($op === self::OPERATION_IMPORT && $service_flag) {
      $ch_connection = $this->achConfigurations->getConnectionDetails();
      if ($ch_connection->getSyndicationMode() !== ConnectionDetailsInterface::SYNDICATION_MODE_PULL) {
        return new CommandError('Service flag should be used in Pull Syndication mode only.', self::EXIT_FAILURE);
      }
    }

    return NULL;
  }

  /**
   * Re-queues entities to queues.
   *
   * If subscriber is in push mode entities are re-enqueued to local queue.
   * If subscriber is in pull mode entities are re-enqueued to Service queue.
   * At least one option has to be provided.
   *
   * @param string $op
   *   The import/export operation.
   *
   * @option type
   *   The entity type.
   * @default type
   * @option uuid
   *   The entity UUID.
   * @default uuid
   * @option bundle
   *   The entity bundle.
   * @default bundle
   * @option use-tracking-table
   *   Only re-queue entities that are in the tracking table
   * If not used, it will enqueue ALL entities of the provided type and bundle.
   * @default use-tracking-table false
   * @option only-queued-entities
   *   Only re-queue entities that have "queued" status in the tracking table
   *   but are not in the export queue.
   * @default only-queued-entities false
   * @option service
   *   Should be used during pull-syndication mode only.
   * @default service false
   * @option reason
   *    (service) Reason for re-enqueuing entities.
   * @default reason manual
   * @option action
   *    (service) Requeue action for entities
   * @default action entity_create
   *
   * @usage ach-rq export
   *   You have to provide at least one option.
   * @usage ach-rq import
   *   You have to provide at least one option.
   * @usage acquia:contenthub-re-queue export --type=node
   *   Requeues all eligible node entities in the publisher site
   * (disregards what has been published before).
   * @usage acquia:contenthub-re-queue import --type=node
   *   Requeues all eligible node entities to the subscriber site
   * @usage acquia:contenthub-re-queue export --type=node --bundle=page
   *   Requeues all eligible node pages in the publisher site
   * (disregards what has been published before).
   * @usage acquia:contenthub-re-queue import --type=node --bundle=page
   *   Requeues all eligible node pages to the subscriber site
   * @usage acquia:contenthub-re-queue export --type=node --uuid=00000aa0-a00a-000a-aa00-aaa000a0a0aa
   *   Requeues entity by UUID in publisher site.
   * (disregards all other options).
   * @usage acquia:contenthub-re-queue import --type=node --uuid=00000aa0-a00a-000a-aa00-aaa000a0a0aa
   *   Requeues entity by UUID to subscriber site.
   * @usage acquia:contenthub-re-queue export --only-queued-entities
   *   Requeues entities with "queued" status in the publisher tracking table.
   * @usage acquia:contenthub-re-queue import --only-queued-entities
   *   Requeues entities with "queued" status in the subscriber tracking table.
   * @usage acquia:contenthub-re-queue export --use-tracking-table
   *   Requeues all existing entities in the publisher tracking table.
   * @usage acquia:contenthub-re-queue import --use-tracking-table
   *   Requeues all existing entities in the subscriber tracking table.
   * @usage acquia:contenthub-re-queue export --type=node --bundle=article --use-tracking-table
   *   Requeues all node article entities that have been previously
   * published (they exist in the publisher tracking table).
   * @usage acquia:contenthub-re-queue import --type=node --bundle=article --use-tracking-table
   *   Requeues all node article entities that exist in the
   * subscriber tracking table.
   *
   * @command acquia:contenthub-re-queue
   * @aliases ach-rq
   *
   * @return int
   *   The exit code.
   *
   * @throws \Exception
   */
  public function enqueueEntities(string $op): int {
    $re_queuer = $this->getRequeuer();
    if (is_null($re_queuer)) {
      $this->io()->error('Could not instantiate re-queuer. Is acquia_contenthub_publisher / acquia_contenthub_subscriber enabled?');
      return self::REQUEUER_ERROR;
    }

    try {
      $failed_items = $re_queuer->reQueue(
        $this->input->getOption('type') ?? '',
        $this->input->getOption('bundle') ?? '',
        $this->input->getOption('uuid') ?? '',
        $this->input->getOption('only-queued-entities'),
        $this->input->getOption('use-tracking-table'),
        $this->input->getOption('reason'),
        $this->input->getOption('action'),
        $this->input->getOption('service')
      );
    }
    catch (\Exception $e) {
      $this->io()->error($e->getMessage());
      return self::REQUEUER_ERROR;
    }

    if (!empty($failed_items)) {
      $this->io()->warning('Some items failed to enqueue:');
      $this->io()->writeln(json_encode($failed_items, JSON_PRETTY_PRINT));
    }

    $this->io()->success('Successfully enqueued entities!');

    return self::EXIT_SUCCESS;
  }

  /**
   * Returns a re-queuer based on the operation.
   *
   * @return \Drupal\acquia_contenthub\RequeuerInterface|null
   *   The re-queuer or NULL.
   */
  protected function getRequeuer(): ?RequeuerInterface {
    $operation = $this->input()->getArgument('op');
    if ($operation === self::OPERATION_EXPORT) {
      return $this->pubRequeuer;
    }

    if ($operation === self::OPERATION_IMPORT) {
      return $this->subRequeuer;
    }

    return NULL;
  }

  /**
   * Checks if the input operation is valid.
   *
   * @param string $operation
   *   Name of the operation.
   *
   * @return bool
   *   True, if operation is valid.
   */
  protected function isValidOperation(string $operation): bool {
    return ($this->pubSubChecker->isPublisher() && $operation === self::OPERATION_EXPORT) ||
      ($this->pubSubChecker->isSubscriber() && $operation === self::OPERATION_IMPORT) ||
      ($this->pubSubChecker->siteHasDualConfiguration() && ($operation === self::OPERATION_EXPORT || $operation === self::OPERATION_IMPORT));
  }

}
