<?php

namespace Drupal\eb\Drush\Commands;

use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\eb\Service\RollbackManagerInterface;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Drush commands for Entity Builder rollback operations.
 *
 * Uses ContentEntity storage (EbRollback, EbRollbackOperation).
 * Rollbacks are definition-level (all operations rolled back together).
 */
final class EbRollbackCommands extends DrushCommands {

  /**
   * Constructor.
   *
   * @param \Drupal\eb\Service\RollbackManagerInterface $rollbackManager
   *   The rollback manager service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The time service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The config factory service.
   * @param \Drupal\Core\Datetime\DateFormatterInterface $dateFormatter
   *   The date formatter service.
   */
  public function __construct(
    protected RollbackManagerInterface $rollbackManager,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected TimeInterface $time,
    protected ConfigFactoryInterface $configFactory,
    protected DateFormatterInterface $dateFormatter,
  ) {
    parent::__construct();
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): self {
    return new self(
      $container->get('eb.rollback_manager'),
      $container->get('entity_type.manager'),
      $container->get('datetime.time'),
      $container->get('config.factory'),
      $container->get('date.formatter'),
    );
  }

  /**
   * List available rollback operations.
   *
   * @param array<string, mixed> $options
   *   Command options.
   */
  #[CLI\Command(name: 'eb:rollback-list', aliases: ['ebrl'])]
  #[CLI\Option(name: 'definition', description: 'Filter by definition ID')]
  #[CLI\Option(name: 'status', description: 'Filter by status (pending, completed, failed)')]
  #[CLI\Usage(name: 'eb:rollback-list', description: 'List all rollback operations')]
  #[CLI\Usage(name: 'eb:rollback-list --definition=my_def', description: 'List rollbacks for definition')]
  #[CLI\Usage(name: 'eb:rollback-list --status=pending', description: 'List pending rollbacks only')]
  #[CLI\FieldLabels(labels: [
    'id' => 'ID',
    'definition' => 'Definition',
    'label' => 'Label',
    'operations' => 'Operations',
    'status' => 'Status',
    'created' => 'Created',
    'user' => 'User',
  ])]
  public function listRollbacks(
    array $options = [
      'definition' => NULL,
      'status' => NULL,
    ],
  ): RowsOfFields {
    $conditions = [];

    if (!empty($options['definition'])) {
      $conditions['definition_id'] = $options['definition'];
    }

    if (!empty($options['status'])) {
      $conditions['status'] = $options['status'];
    }

    $rollbacks = $this->rollbackManager->listRollbacks($conditions);

    $rows = [];
    /** @var \Drupal\eb\Entity\EbRollbackInterface $rollback */
    foreach ($rollbacks as $rollback) {
      $owner = $rollback->getOwner();
      $created = $rollback->get('created')->value;

      $rows[] = [
        'id' => $rollback->id(),
        'definition' => $rollback->getDefinitionId() ?: '-',
        'label' => $rollback->label(),
        'operations' => $rollback->getOperationCount(),
        'status' => $rollback->getStatus(),
        'created' => $created ? $this->dateFormatter->format((int) $created, 'short') : '-',
        'user' => $owner ? $owner->getDisplayName() : 'Unknown',
      ];
    }

    return new RowsOfFields($rows);
  }

  /**
   * Execute a rollback operation by ID.
   *
   * Rolls back ALL operations from the definition apply in reverse order.
   *
   * @param int $id
   *   The rollback entity ID.
   */
  #[CLI\Command(name: 'eb:rollback', aliases: ['ebr'])]
  #[CLI\Argument(name: 'id', description: 'The rollback ID (integer)')]
  #[CLI\Usage(name: 'eb:rollback 123', description: 'Execute rollback with ID 123')]
  public function rollback(int $id): int {
    $rollback = $this->rollbackManager->loadRollback($id);

    if ($rollback === NULL) {
      $this->logger()->error(dt('Rollback not found: @id', ['@id' => $id]));
      return self::EXIT_FAILURE;
    }

    $this->output()->writeln(dt('Rollback: @label', ['@label' => $rollback->label()]));
    $this->output()->writeln(dt('Definition: @def', ['@def' => $rollback->getDefinitionId() ?: '-']));
    $this->output()->writeln(dt('Operations: @count', ['@count' => $rollback->getOperationCount()]));
    $this->output()->writeln(dt('Status: @status', ['@status' => $rollback->getStatus()]));
    $this->output()->writeln('');

    if (!$rollback->isPending()) {
      $this->logger()->error(dt('Rollback is not pending (status: @status)', [
        '@status' => $rollback->getStatus(),
      ]));
      return self::EXIT_FAILURE;
    }

    // @phpstan-ignore-next-line class.notFound
    if (!$this->io()->confirm(dt('Execute this rollback? All @count operations will be reversed.', [
      '@count' => $rollback->getOperationCount(),
    ]))) {
      $this->logger()->notice(dt('Rollback cancelled'));
      return self::EXIT_SUCCESS;
    }

    try {
      $result = $this->rollbackManager->executeRollback($id);

      if ($result->isSuccess()) {
        $this->logger()->success(dt('Rollback executed successfully'));
        return self::EXIT_SUCCESS;
      }

      foreach ($result->getErrors() as $error) {
        $this->logger()->error($error);
      }

      return self::EXIT_FAILURE;
    }
    catch (\Exception $e) {
      $this->logger()->error(dt('Error executing rollback: @message', ['@message' => $e->getMessage()]));
      return self::EXIT_FAILURE;
    }
  }

  /**
   * Rollback all pending operations from a definition.
   *
   * @param string $definitionId
   *   The definition ID.
   * @param array<string, mixed> $options
   *   Command options.
   */
  #[CLI\Command(name: 'eb:rollback-definition', aliases: ['ebrd'])]
  #[CLI\Argument(name: 'definitionId', description: 'The definition ID')]
  #[CLI\Option(name: 'force', description: 'Continue on errors')]
  #[CLI\Usage(name: 'eb:rollback-definition my_def', description: 'Rollback all pending rollbacks from definition')]
  #[CLI\Usage(name: 'eb:rollback-definition my_def --force', description: 'Continue even if some fail')]
  public function rollbackDefinition(
    string $definitionId,
    array $options = ['force' => FALSE],
  ): int {
    // Get all pending rollbacks for this definition.
    $rollbacks = $this->rollbackManager->listRollbacksByDefinition($definitionId, 'pending');

    if (empty($rollbacks)) {
      $this->logger()->notice(dt('No pending rollbacks found for definition: @id', [
        '@id' => $definitionId,
      ]));
      return self::EXIT_SUCCESS;
    }

    $this->output()->writeln('');
    $this->output()->writeln(dt('Found @count pending rollback(s) for definition "@id":', [
      '@count' => count($rollbacks),
      '@id' => $definitionId,
    ]));
    $this->output()->writeln('');

    /** @var \Drupal\eb\Entity\EbRollbackInterface $rollback */
    foreach ($rollbacks as $rollback) {
      $this->output()->writeln(dt('  - @label (@count operations)', [
        '@label' => $rollback->label(),
        '@count' => $rollback->getOperationCount(),
      ]));
    }
    $this->output()->writeln('');

    // @phpstan-ignore-next-line class.notFound
    if (!$this->io()->confirm(dt('Execute all @count rollbacks?', ['@count' => count($rollbacks)]))) {
      $this->logger()->notice(dt('Rollback cancelled'));
      return self::EXIT_SUCCESS;
    }

    // Execute rollbacks in reverse order (most recent first).
    $successCount = 0;
    $errorCount = 0;

    // Sort by ID descending (most recent first).
    $rollbacks = array_reverse($rollbacks, TRUE);

    /** @var \Drupal\eb\Entity\EbRollbackInterface $rollback */
    foreach ($rollbacks as $rollback) {
      try {
        $rollbackId = (int) $rollback->id();
        $result = $this->rollbackManager->executeRollback($rollbackId);

        if ($result->isSuccess()) {
          $this->logger()->success(dt('Rolled back: @label', [
            '@label' => $rollback->label(),
          ]));
          $successCount++;
        }
        else {
          $errorCount++;
          foreach ($result->getErrors() as $error) {
            $this->logger()->error($error);
          }

          if (!$options['force']) {
            $this->logger()->error(dt('Stopping due to error. Use --force to continue.'));
            break;
          }
        }
      }
      catch (\Exception $e) {
        $errorCount++;
        $this->logger()->error(dt('Error: @message', ['@message' => $e->getMessage()]));

        if (!$options['force']) {
          $this->logger()->error(dt('Stopping due to error. Use --force to continue.'));
          break;
        }
      }
    }

    $this->output()->writeln('');
    $this->output()->writeln(dt('Results: @success succeeded, @errors failed', [
      '@success' => $successCount,
      '@errors' => $errorCount,
    ]));

    return $errorCount > 0 ? self::EXIT_FAILURE : self::EXIT_SUCCESS;
  }

  /**
   * Purge old rollback operations.
   *
   * Deletes rollback operations first (referential integrity), then rollbacks.
   *
   * @param int|null $days
   *   Delete rollbacks older than this many days. Uses config default if NULL.
   */
  #[CLI\Command(name: 'eb:rollback-purge', aliases: ['ebrp'])]
  #[CLI\Argument(name: 'days', description: 'Delete rollbacks older than this many days (default from settings)')]
  #[CLI\Usage(name: 'eb:rollback-purge', description: 'Delete rollbacks using configured retention days')]
  #[CLI\Usage(name: 'eb:rollback-purge 30', description: 'Delete rollbacks older than 30 days')]
  public function purgeRollbacks(?int $days = NULL): int {
    if ($days === NULL) {
      $days = $this->configFactory->get('eb.settings')->get('rollback_retention_days') ?? 30;
    }

    $cutoff = $this->time->getRequestTime() - ($days * 86400);
    $cutoffDate = $this->dateFormatter->format($cutoff, 'short');

    // Count rollbacks to delete.
    $storage = $this->entityTypeManager->getStorage('eb_rollback');
    $query = $storage->getQuery()
      ->condition('created', $cutoff, '<')
      ->accessCheck(FALSE);

    $ids = $query->execute();

    if (empty($ids)) {
      $this->logger()->notice(dt('No rollbacks older than @days days found', ['@days' => $days]));
      return self::EXIT_SUCCESS;
    }

    $this->output()->writeln(dt('Found @count rollback(s) older than @days days (before @date)', [
      '@count' => count($ids),
      '@days' => $days,
      '@date' => $cutoffDate,
    ]));

    // @phpstan-ignore-next-line class.notFound
    if (!$this->io()->confirm(dt('Delete these rollbacks and their operations?'))) {
      $this->logger()->notice(dt('Purge cancelled'));
      return self::EXIT_SUCCESS;
    }

    try {
      $deleted = $this->rollbackManager->purgeOldRollbacks($days);

      $this->logger()->success(dt('Deleted @count rollback(s) and their operations', ['@count' => $deleted]));
      return self::EXIT_SUCCESS;
    }
    catch (\Exception $e) {
      $this->logger()->error(dt('Error purging rollbacks: @message', ['@message' => $e->getMessage()]));
      return self::EXIT_FAILURE;
    }
  }

}
