<?php

namespace Drupal\acquia_contenthub\Commands;

use Acquia\ContentHubClient\ContentHubClient;
use Acquia\ContentHubClient\Syndication\SyndicationStatus;
use Drupal\acquia_contenthub\Client\ClientFactory;
use Drupal\acquia_contenthub\Commands\Traits\ColorizedOutputTrait;
use Drupal\acquia_contenthub\ContentHubConnectionManager;
use Drupal\acquia_contenthub\Libs\InterestList\InterestListTrait;
use Drupal\acquia_contenthub\PubSubModuleStatusChecker;
use Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface;
use Drush\Commands\DrushCommands;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\HttpFoundation\Response;

/**
 * Tests commands that interact with webhook interests.
 *
 * @package Drupal\acquia_contenthub\Commands
 */
class AcquiaContentHubWebhookInterestCommands extends DrushCommands {

  use InterestListTrait;
  use ColorizedOutputTrait;

  /**
   * Allowed site Roles.
   */
  public const SITE_ROLES = [
    'publisher',
    'subscriber',
  ];

  /**
   * The client factory.
   *
   * @var \Drupal\acquia_contenthub\Client\ClientFactory
   */
  protected $clientFactory;

  /**
   * The Content Hub client.
   *
   * @var \Acquia\ContentHubClient\ContentHubClient
   */
  protected $client;

  /**
   * The webhook.
   *
   * @var \Acquia\ContentHubClient\Webhook|array
   */
  protected $webhook;

  /**
   * The Content Hub Connection Manager.
   *
   * @var \Drupal\acquia_contenthub\ContentHubConnectionManager
   */
  protected $connectionManager;

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

  /**
   * Status Checker.
   *
   * @var \Drupal\acquia_contenthub\PubSubModuleStatusChecker
   */
  protected $checker;

  /**
   * AcquiaContentHubWebhookInterestCommands constructor.
   *
   * @param \Drupal\acquia_contenthub\Client\ClientFactory $client_factory
   *   The client factory.
   * @param \Drupal\acquia_contenthub\ContentHubConnectionManager $connection_manager
   *   The Content Hub Connection Manager.
   * @param \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ach_configuration
   *   CH Configurations.
   * @param \Drupal\acquia_contenthub\PubSubModuleStatusChecker $checker
   *   Status checker.
   */
  public function __construct(ClientFactory $client_factory, ContentHubConnectionManager $connection_manager, ContentHubConfigurationInterface $ach_configuration, PubSubModuleStatusChecker $checker) {
    $this->clientFactory = $client_factory;
    $this->connectionManager = $connection_manager;
    $this->achConfigurations = $ach_configuration;
    $this->checker = $checker;
  }

  /**
   * Find webhook information.
   *
   * @param string $option_name
   *   Option name to look for webhook.
   *
   * @throws \Exception
   */
  private function findWebhook(string $option_name = 'webhook_url'):void {
    $this->client = $this->clientFactory->getClient();
    if (!$this->client) {
      throw new \Exception(dt('The Content Hub client is not connected so the webhook operations could not be performed.'));
    }

    $url = $this->formatWebhookUrl($this->input()->getOption($option_name) ?? $this->client->getSettings()->getWebhook('url'));
    $this->webhook = $this->client->getWebHook($url);
    if (!$this->webhook) {
      throw new \Exception(dt('The webhook is not available so the operation could not complete.'));
    }
  }

  /**
   * Perform a webhook interest management operation.
   *
   * @command acquia:contenthub-webhook-interests-list
   * @aliases ach-wi-list
   *
   * @option webhook-url
   *   The webhook URL to use.
   * @default webhook-url null
   * @option format
   *   The format to use. E.g. 'table' or 'json'
   * @default format table
   * @option site-role
   *   The site against which to perform the operation.
   * @default site-role null
   * @option uuid
   *   Uuid to filter with.
   * @default uuid null
   * @option reason
   *   Reason to filter with.
   * @default reason null
   * @option syndication-status
   *   Syndication status to filter with.
   * @default syndication-status null
   * @option disable-syndication
   *   Disable syndication flag. Either true or false.
   * @default disable-syndication null
   *
   * @usage acquia:contenthub-webhook-interests-list
   *   Displays list of registered interests for the webhook
   *
   * @throws \Exception
   */
  public function contenthubWebhookInterestsList() {
    $site_role = $this->checkSiteRole('site-role');

    if (empty($site_role)) {
      return;
    }

    $this->findWebhook('webhook-url');
    $interests = $this->client->getInterestList($this->webhook->getUuid(), $site_role);

    if (empty($interests)) {
      $this->output()->writeln(dt('<fg=white;bg=red;options=bold;>No interests found for webhook @url</>', ['@url' => $this->webhook->getUrl()]));
      return;
    }

    $interests = $this->formatInterests($interests);
    $interests = $this->filterInterests($interests);
    $format = $this->input->getOption('format');
    $this->renderInterests($interests, $format);
  }

  /**
   * Perform a webhook interest management operation.
   *
   * @command acquia:contenthub-webhook-interests-add
   * @aliases ach-wi-add
   *
   * @option webhook_url
   *   The webhook URL to use.
   * @default webhook_url null
   * @option uuids
   *   The entities against which to perform the operation. Comma-separated.
   * @default uuids null
   * @option site_role
   *   The site against which to perform the operation.
   * @default site_role null
   *
   * @usage acquia:contenthub-webhook-interests-add
   *   Add the interests to the webhook
   *
   * @throws \Exception
   */
  public function contenthubWebhookInterestsAdd() {
    $site_role = $this->checkSiteRole();

    if (empty($site_role)) {
      return;
    }

    $this->findWebhook();

    $uuids = $this->input()->getOptions()['uuids'];
    if (empty($uuids)) {
      $this->output()->writeln(dt('<fg=white;bg=red;options=bold;>[error] Uuids are required to add interests.</>'));
      return;
    }

    $uuids = explode(',', $uuids);
    $send_update = $this->achConfigurations->getContentHubConfig()->shouldSendContentHubUpdates();
    if ($send_update) {

      $reason = $site_role === 'PUBLISHER' ? '' : 'manual';
      $interest_list = $this->buildInterestList($uuids, SyndicationStatus::EXPORT_SUCCESSFUL, $reason);
      $response = $this->client->addEntitiesToInterestListBySiteRole($this->webhook->getUuid(), $site_role, $interest_list);

      if (!$response) {
        $this->output()->writeln(dt('Empty response after attempting to add entities to interest list.'));
        return;
      }

      if (200 !== $response->getStatusCode()) {
        $resp = ContentHubClient::getResponseJson($response);
        $this->output()->writeln(dt(
          'An error occurred and interests were not updated. Error message: @error',
          ['@error' => $resp['error']['message'] ?? 'Unknown error']
        ));
        return;
      }
      $this->output()->writeln(dt("\nInterests updated successfully.\n"));
    }

    $this->contenthubWebhookInterestsList();
  }

  /**
   * Perform a webhook interest management operation.
   *
   * @command acquia:contenthub-webhook-interests-delete
   * @aliases ach-wi-del
   *
   * @option webhook_url
   *   The webhook URL to use.
   * @default webhook_url null
   * @option uuids
   *   The entities against which to perform the operation. Comma-separated.
   * @default uuids null
   * @option site_role
   *   The site against which to perform the operation.
   * @default site_role null
   *
   * @usage acquia:contenthub-webhook-interests-delete
   *   Delete the interest from the webhook
   *
   * @throws \Exception
   */
  public function contenthubWebhookInterestsDelete(): int {
    $this->findWebhook();
    $site_role = $this->checkSiteRole();
    if (empty($this->input()->getOptions()['uuids'])) {
      $this->output()->writeln($this->error(dt('Uuids are required to delete interests.')));
      return 2;
    }
    $uuids = explode(',', $this->input()->getOptions()['uuids']);
    $send_update = $this->achConfigurations->getContentHubConfig()->shouldSendContentHubUpdates();
    if ($send_update) {
      $this->output()->writeln(dt('Removing interests from webhook @webhook.', [
        '@webhook' => $this->webhook->getUrl(),
      ]));
      $response = $this->client->deleteMultipleInterest($this->webhook->getUuid(), $uuids, $site_role);
      if ($response->getStatusCode() !== Response::HTTP_OK) {
        $this->output()->writeln($this->error(dt('Could not remove interest list: @e_message', [
          '@e_message' => (string) $response->getBody(),
        ])));
        return 3;
      }
      $this->output()->writeln($this->info(t('Interests deleted successfully.')));
      return 0;
    }
    $this->output()->writeln($this->error(dt('Content Hub updates are disabled. Please enable them to delete interests.')));
    return 2;
  }

  /**
   * Synchronizes Webhook's interest list with Entity Tracking tables.
   *
   * @command acquia:sync-interests
   * @aliases ach-wi-sync
   */
  public function syncInterestListWithTrackingTable() {
    $this->connectionManager->syncWebhookInterestListWithTrackingTables();
  }

  /**
   * Render interest data in given format.
   *
   * @param array $interests
   *   Columns of interest data to render into rows.
   * @param string $format
   *   Format to use for printing interest data. E.g. 'table', 'json'.
   */
  private function renderInterests(array $interests, string $format): void {
    if ($format === 'json') {
      $this->output()->writeln(json_encode(['interests' => $interests]));
      return;
    }
    // Table view.
    if (count($interests) === 0) {
      $this->io()->warning('No interests available for this webhook for given filters');
      return;
    }
    $message = sprintf('Listing Interests for webhook %s', $this->webhook->getUrl());
    $this->output()->writeln($message);
    $table = new Table($this->output());
    $headers = [
      'Index',
      'Interest',
      'Syndication status',
      'Reason',
      'Disabled for Syndication',
    ];
    $table->setHeaders($headers);
    $index = 0;
    array_walk($interests, static function (&$row) use (&$index) {
      array_unshift($row, ++$index);
    });
    $table->setRows($interests)
      ->render();
  }

  /**
   * Format webhook url in case of missing acquia-contenthub/webhook.
   *
   * @param string $webhook_url
   *   Webhook url to format.
   *
   * @return string
   *   Webhook url in proper format.
   */
  private function formatWebhookUrl(string $webhook_url) {
    if (!strpos($webhook_url, 'acquia-contenthub/webhook')) {
      $webhook_url .= 'acquia-contenthub/webhook';
    }
    return $webhook_url;
  }

  /**
   * Validates the site role option and returns relevant site role.
   *
   * @param string $option_name
   *   Option name to look for site role.
   *
   * @return string
   *   Site role.
   */
  private function checkSiteRole(string $option_name = 'site_role'): string {
    $site_role = strtolower($this->input()->getOption($option_name));
    if (empty($site_role)) {
      $this->io()->error(dt('Site role is required to add/fetch interests.'));
      return '';
    }

    if (!in_array($site_role, self::SITE_ROLES, TRUE)) {
      $this->io()->error(dt('Invalid site role.'));
      return '';
    }

    if ($site_role === 'publisher' && !$this->checker->isPublisher()) {
      $this->io()->error(dt('Current site is not a publisher.'));
      return '';
    }

    if ($site_role === 'subscriber' && !$this->checker->isSubscriber()) {
      $this->io()->error(dt('Current site is not a subscriber.'));
      return '';
    }

    return $site_role;
  }

  /**
   * Formats interest data in render-able format.
   *
   * @param array $interests
   *   Interest data.
   *
   * @return array
   *   Formatted interests.
   */
  private function formatInterests(array $interests): array {
    $rows = [];
    foreach ($interests as $uuid => $interest) {
      $rows[] = [
        $uuid,
        $interest['status'] ?? '',
        $interest['reason'] ?? 'N/A',
        $interest['disable_syndication'] ?? FALSE ? 'TRUE' : 'FALSE',
      ];
    }
    return $rows;
  }

  /**
   * Filter interests based on input options.
   *
   * @param array $interests
   *   Interests to filter.
   *
   * @return array
   *   Filtered interests.
   */
  private function filterInterests(array $interests): array {
    $uuid = $this->input->getOption('uuid');
    $reason = $this->input->getOption('reason');
    $disable_syndication = $this->input->getOption('disable-syndication');
    if ($disable_syndication) {
      $disable_syndication = strtolower($disable_syndication) === 'true' ? 'TRUE' : 'FALSE';
    }
    $syndication_status = $this->input->getOption('syndication-status');
    $filtered_interests = [];
    foreach ($interests as $interest) {
      if (
        (isset($uuid) && $interest[0] !== $uuid) ||
        (isset($syndication_status) && $interest[1] !== $syndication_status) ||
        (isset($reason) && $interest[2] !== $reason) ||
        (isset($disable_syndication) && $interest[3] !== $disable_syndication)
      ) {
        continue;
      }

      $filtered_interests[] = $interest;
    }
    return $filtered_interests;
  }

}
