<?php

namespace Drupal\eb\Plugin\EbOperation;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\eb\Attribute\EbOperation;
use Drupal\eb\Exception\ExecutionException;
use Drupal\eb\PluginBase\OperationBase;
use Drupal\eb\Result\ExecutionResult;
use Drupal\eb\Result\PreviewResult;
use Drupal\eb\Result\RollbackResult;
use Drupal\eb\Result\ValidationResult;
use Drupal\eb\Service\DiscoveryServiceInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Operation for updating entity bundles.
 */
#[EbOperation(
  id: 'update_bundle',
  label: new TranslatableMarkup('Update Bundle'),
  description: new TranslatableMarkup('Updates an existing bundle configuration'),
  operationType: 'update',
)]
class UpdateBundleOperation extends OperationBase {

  /**
   * Constructor.
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    mixed $plugin_definition,
    EntityTypeManagerInterface $entityTypeManager,
    LoggerInterface $logger,
    ConfigFactoryInterface $configFactory,
    protected DiscoveryServiceInterface $discoveryService,
    protected EntityFieldManagerInterface $entityFieldManager,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $entityTypeManager, $logger, $configFactory);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    /** @var \Psr\Log\LoggerInterface $logger */
    $logger = $container->get('logger.channel.eb');
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_type.manager'),
      $logger,
      $container->get('config.factory'),
      $container->get('eb.discovery_service'),
      $container->get('entity_field.manager'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function validate(): ValidationResult {
    $result = new ValidationResult();

    $this->validateRequiredFields(['entity_type', 'bundle_id'], $result);

    if (!$result->isValid()) {
      return $result;
    }

    $entity_type = $this->getDataValue('entity_type');
    $bundle_id = $this->getDataValue('bundle_id');

    // Validate bundle exists.
    $validation = $this->discoveryService->validateEntityBundle($entity_type, $bundle_id);
    if (!$validation['valid']) {
      foreach ($validation['errors'] as $error) {
        $result->addError($error, 'bundle_id', 'bundle_not_found');
      }
    }

    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function preview(): PreviewResult {
    $preview = new PreviewResult();

    $entity_type = $this->getDataValue('entity_type');
    $bundle_id = $this->getDataValue('bundle_id');

    $preview->addOperation(
      'update',
      'bundle',
      $bundle_id,
      $this->t('Update bundle @bundle for @type', [
        '@bundle' => $bundle_id,
        '@type' => $entity_type,
      ])
    );

    $details = ['Entity Type' => $entity_type, 'Bundle ID' => $bundle_id];

    if ($label = $this->getDataValue('label')) {
      $details['New Label'] = $label;
    }

    if ($description = $this->getDataValue('description')) {
      $details['New Description'] = $description;
    }

    $preview->addDetails($details);

    return $preview;
  }

  /**
   * {@inheritdoc}
   */
  public function execute(): ExecutionResult {
    $entity_type = $this->getDataValue('entity_type');
    $bundle_id = $this->getDataValue('bundle_id');

    return $this->executeWithErrorHandling(function () use ($entity_type, $bundle_id) {
      $entity_type_definitions = $this->discoveryService->getAllEntityTypes();
      $bundle_entity_type = $entity_type_definitions[$entity_type]['bundle_entity_type'];

      $bundle_storage = $this->entityTypeManager->getStorage($bundle_entity_type);
      $bundle = $bundle_storage->load($bundle_id);

      if (!$bundle) {
        throw new ExecutionException("Bundle '{$bundle_id}' not found");
      }

      // Store original configuration.
      $original_config = $bundle->toArray();

      // Update fields.
      // Note: NodeType uses 'name' for the label, while other bundle entities
      // may use 'label'. Check entity keys to determine correct property.
      if ($label = $this->getDataValue('label')) {
        $entity_type_def = $this->entityTypeManager->getDefinition($bundle_entity_type);
        $label_key = $entity_type_def->getKey('label');
        // @phpstan-ignore-next-line
        $bundle->set($label_key ?: 'name', $label);
      }

      if ($description = $this->getDataValue('description')) {
        // @phpstan-ignore-next-line
        $bundle->set('description', $description);
      }

      if ($settings = $this->getDataValue('settings')) {
        foreach ($settings as $key => $value) {
          // @phpstan-ignore-next-line
          $bundle->set($key, $value);
        }
      }

      $bundle->save();

      $result = new ExecutionResult(TRUE);
      $result->addAffectedEntityById($bundle_entity_type, $bundle_id);
      $result->setRollbackData([
        'bundle_entity_type' => $bundle_entity_type,
        'bundle_id' => $bundle_id,
        'original_config' => $original_config,
      ]);

      $this->logInfo('Updated bundle: @bundle', ['@bundle' => $bundle_id]);

      return $result;
    }, 'update bundle');
  }

  /**
   * {@inheritdoc}
   */
  public function rollback(): RollbackResult {
    $rollback_data = $this->getRequiredRollbackData();

    return $this->rollbackWithErrorHandling(function () use ($rollback_data) {
      $bundle_storage = $this->entityTypeManager->getStorage($rollback_data['bundle_entity_type']);
      $bundle = $bundle_storage->load($rollback_data['bundle_id']);

      if ($bundle) {
        foreach ($rollback_data['original_config'] as $key => $value) {
          // @phpstan-ignore-next-line
          $bundle->set($key, $value);
        }
        $bundle->save();
      }

      $this->logInfo('Rolled back bundle update: @bundle', ['@bundle' => $rollback_data['bundle_id']]);

      $result = new RollbackResult(TRUE);
      $result->addMessage($this->t('Successfully rolled back bundle update'));

      return $result;
    }, 'bundle update');
  }

}
