<?php

namespace Drupal\acquia_contenthub\Commands;

use Consolidation\AnnotatedCommand\CommandError;
use Drupal\acquia_contenthub\Client\CdfMetricsManager;
use Drupal\acquia_contenthub\Client\ClientFactory;
use Drupal\acquia_contenthub\ContentHubCommonActions;
use Drupal\acquia_contenthub\ContentHubConnectionManager;
use Drupal\acquia_contenthub\Exception\EntityOriginMismatchException;
use Drupal\acquia_contenthub\Libs\Traits\ResponseCheckerTrait;
use Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface;
use Drupal\Component\Uuid\Uuid;
use Drush\Commands\DrushCommands;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Validator\Exception\MissingOptionsException;

/**
 * Drush commands for Client related operations.
 *
 * @package Drupal\acquia_contenthub\Commands
 */
class AcquiaContentHubClientCommands extends DrushCommands {

  use ResponseCheckerTrait;

  /**
   * Default cloud filter prefix.
   *
   * @var string
   */
  const DEFAULT_FILTER = 'default_filter_';

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

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

  /**
   * The contenthub client object.
   *
   * @var \Acquia\ContentHubClient\ContentHubClient|bool
   */
  protected $client;

  /**
   * CH connection manager.
   *
   * @var \Drupal\acquia_contenthub\ContentHubConnectionManager
   */
  protected ContentHubConnectionManager $connectionManager;

  /**
   * Content Hub CDF metrics manager service.
   *
   * @var \Drupal\acquia_contenthub\Client\CdfMetricsManager
   */
  protected CdfMetricsManager $metricsManager;

  /**
   * The Content Hub Common Actions.
   *
   * @var \Drupal\acquia_contenthub\ContentHubCommonActions
   */
  protected ContentHubCommonActions $commonActions;

  /**
   * Constructor.
   *
   * @param \Drupal\acquia_contenthub\Settings\ContentHubConfigurationInterface $ach_configurations
   *   The configuration factory.
   * @param \Drupal\acquia_contenthub\Client\ClientFactory $client_factory
   *   ACH client factory.
   * @param \Drupal\acquia_contenthub\ContentHubConnectionManager $connection_manager
   *   CH connection manager.
   * @param \Drupal\acquia_contenthub\Client\CdfMetricsManager $metrics_manager
   *   Content Hub cdf metrics manager.
   * @param \Drupal\acquia_contenthub\ContentHubCommonActions $common_actions
   *   The Content Hub Common Actions service.
   */
  public function __construct(ContentHubConfigurationInterface $ach_configurations, ClientFactory $client_factory, ContentHubConnectionManager $connection_manager, CdfMetricsManager $metrics_manager, ContentHubCommonActions $common_actions) {
    $this->achConfigurations = $ach_configurations;
    $this->clientFactory = $client_factory;
    $this->connectionManager = $connection_manager;
    $this->metricsManager = $metrics_manager;
    $this->commonActions = $common_actions;
  }

  /**
   * Check if Content Hub Client is available.
   *
   * @hook validate
   *
   * @throws \Exception
   */
  public function checkClient(): ?CommandError {
    $this->client = $this->clientFactory->getClient();
    if ($this->client) {
      return NULL;
    }
    return new CommandError('Client is not available. Please check configuration or register the client!', self::EXIT_FAILURE);
  }

  /**
   * Gets Client Details.
   *
   * Retrieves client details along with metadata using client UUID or name.
   *
   * @param string $client_uuid_or_name
   *   The client UUID or name.
   *
   * @command acquia:contenthub:client
   * @aliases ach-client
   * @validate checkClient
   *
   * @usage drush acquia:contenthub:client 550e8400-e29b-41d4-a716-446655440000
   *   Gets the Acquia Content Hub Client details using UUID.
   * @usage drush acquia:contenthub:client my-publisher
   *   Gets the Acquia Content Hub Client details using client name.
   */
  public function getClient(string $client_uuid_or_name): int {
    try {
      $client_data = $this->fetchClientData($client_uuid_or_name);
      if ($client_data) {
        unset($client_data['success'], $client_data['request_id']);
        $this->output()->writeln(json_encode($client_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
        return self::EXIT_SUCCESS;
      }
      return self::EXIT_FAILURE;
    }
    catch (\Exception $e) {
      $this->io()->error($e->getMessage());
      return self::EXIT_FAILURE;
    }
  }

  /**
   * Fetches client data by UUID or name.
   *
   * @param string $client_uuid_or_name
   *   The client UUID or name.
   *
   * @return array
   *   The client data array.
   *
   * @throws \Exception
   */
  protected function fetchClientData(string $client_uuid_or_name): array {
    $is_uuid = Uuid::isValid($client_uuid_or_name);
    $response = $is_uuid ? $this->client->getClientByUuidProperly($client_uuid_or_name) : $this->client->getClientByName($client_uuid_or_name);
    if (!$this->isResponseSuccessful($response)) {
      $error_message = $response['error']['message'] ?? 'Unknown error occurred';
      $this->io()->error(sprintf('Failed to retrieve client by %s: %s', $is_uuid ? 'UUID' : 'name', $error_message));
      return [];
    }
    return $response;
  }

  /**
   * Deletes Client(s).
   *
   * Deletes clients using names or uuids.
   *
   * @command acquia:contenthub:client-delete
   * @aliases ach-cd
   * @validate checkClient
   *
   * @option names
   *    Names of the clients to be deleted.
   * @default names ''
   * @option uuids
   *     UUIDS of the clients to be deleted.
   * @default uuids ''
   * @option ignore-origin
   *    Ignores origin check while deleting client CDF.
   *
   * @usage drush acquia:contenthub-client-delete --names "name1,name2"
   *   Deletes the Acquia Content Hub Client using names.
   * @usage drush acquia:contenthub-client-delete --uuids "uuid1,uuid2"
   *    Deletes the Acquia Content Hub Client using uuids.
   *
   * @throws \Exception
   */
  public function deleteClient(): int {
    $names = $this->input()->getOption('names');
    $uuids = $this->input()->getOption('uuids');

    if (empty($names) && empty($uuids)) {
      $this->io()->warning('No clients to delete.');
      return self::EXIT_FAILURE;
    }

    if (!$this->printConfirmationTable(explode(',', $names), explode(',', $uuids))) {
      return self::EXIT_FAILURE;
    }

    $uuids_list = [];
    if (!empty($names)) {
      $clients = $this->client->getRemoteSettings()['clients'];
      $name_list = explode(',', $names);
      $uuids_by_name = array_filter(array_map(function ($client) use ($name_list) {
        return in_array($client['name'], $name_list) ? $client['uuid'] : NULL;
      }, $clients));
      $uuids_list = array_merge($uuids_list, $uuids_by_name);
    }

    if (!empty($uuids)) {
      $uuids_list = array_merge($uuids_list, explode(',', $uuids));
    }

    if (!empty($uuids_list)) {
      $this->deleteClientByUuids($uuids_list);
    }
    else {
      $this->io()->warning('No clients to delete.');
    }

    return self::EXIT_SUCCESS;
  }

  /**
   * Prints clients names and uuid in tabular format.
   *
   * @param array $names
   *   Names of the clients to be deleted.
   * @param array $uuids
   *   UUIDS of the clients to be deleted.
   *
   * @return int
   *   Returns int as per user's input.
   */
  protected function printConfirmationTable(array $names = [], array $uuids = []): int {
    $table = new Table($this->output());
    $table->setHeaders([
      'Names',
      'UUIDS',
    ]);
    $table->addRow([implode("\n", $names), implode("\n", $uuids)]);
    $table->render();
    return $this->io()->confirm(dt('The above listed clients are going to be deleted, proceed?'));
  }

  /**
   * Prints deleted UUIDs.
   *
   * @param string $deleted_uuids
   *   Lists uuids that are deleted.
   */
  protected function printDeletedClients(string $deleted_uuids): void {
    $this->io()->success("The following clients have been deleted.");
    $table = new Table($this->output());
    $table->setHeaders([
      'Deleted UUIDS',
    ]);
    $table->addRow([$deleted_uuids]);
    $table->render();
  }

  /**
   * Deletes client and client entities using their uuid.
   *
   * @param array $uuids_list
   *   Uuids of the clients to be deleted.
   *
   * @throws \Exception
   *   Throws exception if client could not be deleted.
   */
  protected function deleteClientByUuids(array $uuids_list): void {
    $deleted_uuids = '';
    foreach ($uuids_list as $uuid) {
      try {
        $result = $this->commonActions->deleteRemoteEntity($uuid, $this->input()->getOption('ignore-origin'));
        if ($result === TRUE) {
          $this->output()->writeln(dt('Entity with UUID = @uuid has been successfully deleted from the Content Hub Service.', [
            '@uuid' => $uuid,
          ]));
        }
        else {
          $this->output()->writeln(dt('WARNING: Entity with UUID = @uuid cannot be deleted from the Content Hub Service.', [
            '@uuid' => $uuid,
          ]));
        }
        $response = $this->client->delete("settings/client/uuid/$uuid");

        // Check the response status and provide feedback to the user.
        if ($response->getStatusCode() === 200) {
          $deleted_uuids = $deleted_uuids . "\n" . $uuid;
          $this->io()->success("Client with uuid $uuid deleted successfully.");
        }
        else {
          $this->io()->error("Client with uuid $uuid could not be deleted. Reason: " . $response->getReasonPhrase());
        }
      }
      catch (EntityOriginMismatchException $e) {
        $this->output()->writeln($e->getMessage() . 'Use --ignore-origin flag to ignore origin check.');
      }
      catch (\Exception $e) {
        $this->io()->error($e->getMessage());
      }
    }
    if (!empty($deleted_uuids)) {
      $this->printDeletedClients($deleted_uuids);
    }
  }

  /**
   * Updates Client Details.
   *
   * Updates default filter name, if client update is successful.
   *
   * @command acquia:contenthub:client-update
   * @aliases ach-cu
   * @validate checkClient
   *
   * @option name
   *    The new client name.
   * @default name
   *
   * @usage acquia:contenthub:client-update --name new_name
   *   Updates the Acquia Content Hub Client Name.
   *
   * @throws \Exception
   */
  public function updateClient(): int {
    try {
      $this->updateClientName();
    }
    catch (\Exception $e) {
      $this->io()->error($e->getMessage());
      return self::EXIT_FAILURE;
    }

    return self::EXIT_SUCCESS;
  }

  /**
   * Updates Client Name.
   *
   * @throws \Exception
   *   Throws exception if client could not be updated.
   */
  protected function updateClientName(): void {
    $name = $this->input()->getOption('name');
    if (empty($name)) {
      throw new MissingOptionsException('Name option not passed, new client name is required to run client-update command ', []);
    }
    $ch_connection = $this->achConfigurations->getConnectionDetails();
    if ($ch_connection->isOverridden('client_name')) {
      $this->io()->warning('Client Name is manually changed in settings.php. Drush command can not override it');
      return;
    }
    $client_name = $this->achConfigurations->getConnectionDetails()->getClientName();
    $response = $this->client->updateClient($ch_connection->getClientUuid(), $name);
    if (!$this->isSuccessful($response)) {
      throw new \Exception(sprintf('Client update has failed: %s', $response->getBody()));
    }
    $ch_connection->setClientName($name);
    $this->io()->success('Client name changed successfully.');

    // Refresh client object with recently saved settings.
    $this->client = $this->clientFactory->getClient($this->achConfigurations->getSettings());
    $this->metricsManager->sendClientCdfUpdates($this->client);

    $old_default_filter_name = self::DEFAULT_FILTER . $client_name;
    $new_default_filter_name = self::DEFAULT_FILTER . $name;
    $this->updateDefaultFilter($old_default_filter_name, $new_default_filter_name);
  }

  /**
   * Updates default filter.
   *
   * @param string $old_default_filter_name
   *   Name of old default filter.
   * @param string $new_default_filter_name
   *   Name of a new default filter.
   */
  protected function updateDefaultFilter(string $old_default_filter_name, string $new_default_filter_name): void {
    $response = $this->connectionManager->updateFilterName($old_default_filter_name, $new_default_filter_name);

    if (!$response) {
      return;
    }
    if ($response['success']) {
      $this->io()->success('Default filter updated successfully.');
      return;
    }
    if (!$response['success'] && !empty($response['error']['message'])) {
      $this->io()->error(sprintf('Default filter update failed. Error: %s', $response['error']['message']));
    }
  }

}
