<?php

declare(strict_types=1);

namespace Drupal\flowdrop_session\Plugin\QueueWorker;

use Drupal\Core\Queue\QueueWorkerBase;
use Drupal\Core\Queue\RequeueException;
use Drupal\Core\Queue\SuspendQueueException;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\flowdrop_session\Entity\FlowDropSessionMessageInterface;
use Drupal\flowdrop_session\Service\MessageProcessorInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Queue worker for processing session messages.
 *
 * Processes messages asynchronously when execution mode is set to async.
 *
 * @QueueWorker(
 *   id = "flowdrop_session_message",
 *   title = @Translation("FlowDrop Session Message"),
 *   cron = {"time" = 60}
 * )
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class SessionMessageWorker extends QueueWorkerBase implements ContainerFactoryPluginInterface {

  /**
   * The message processor.
   *
   * @var \Drupal\flowdrop_session\Service\MessageProcessorInterface
   */
  protected MessageProcessorInterface $messageProcessor;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $entityTypeManager;

  /**
   * The logger factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected LoggerChannelFactoryInterface $loggerFactory;

  /**
   * Constructs a SessionMessageWorker.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\flowdrop_session\Service\MessageProcessorInterface $session_service
   *   The message processor.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger factory.
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    $plugin_definition,
    MessageProcessorInterface $session_service,
    EntityTypeManagerInterface $entity_type_manager,
    LoggerChannelFactoryInterface $logger_factory,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->messageProcessor = $session_service;
    $this->entityTypeManager = $entity_type_manager;
    $this->loggerFactory = $logger_factory;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(
    ContainerInterface $container,
    array $configuration,
    $plugin_id,
    $plugin_definition,
  ): static {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get("flowdrop_session.execution_service"),
      $container->get("entity_type.manager"),
      $container->get("logger.factory"),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function processItem($data): void {
    try {
      if (!isset($data["message_id"])) {
        throw new \InvalidArgumentException("Message ID is required");
      }

      $messageId = $data["message_id"];

      // Load the message.
      $messageStorage = $this->entityTypeManager->getStorage("flowdrop_session_message");
      $message = $messageStorage->load($messageId);

      if (!$message instanceof FlowDropSessionMessageInterface) {
        throw new \RuntimeException("Message {$messageId} not found");
      }

      $this->loggerFactory->get("flowdrop_session")->info("Processing message @id from queue", [
        "@id" => $messageId,
      ]);

      // Process the message.
      $this->messageProcessor->processMessage($message);

      $this->loggerFactory->get("flowdrop_session")->info("Successfully processed message @id", [
        "@id" => $messageId,
      ]);
    }
    catch (\Exception $e) {
      $this->loggerFactory->get("flowdrop_session")->error("Failed to process message: @message", [
        "@message" => $e->getMessage(),
      ]);

      if ($this->isTemporaryFailure($e)) {
        throw new RequeueException($e->getMessage());
      }

      if ($this->isPermanentFailure($e)) {
        throw new SuspendQueueException($e->getMessage());
      }

      $this->loggerFactory->get("flowdrop_session")->error("Message processing failed: @message", [
        "@message" => $e->getMessage(),
      ]);
    }
  }

  /**
   * Check if the exception represents a temporary failure.
   *
   * @param \Exception $e
   *   The exception to check.
   *
   * @return bool
   *   TRUE if the failure is temporary.
   */
  protected function isTemporaryFailure(\Exception $e): bool {
    $temporaryExceptions = [
      "PDOException",
      "DatabaseException",
      "ConnectionException",
      "TimeoutException",
    ];

    foreach ($temporaryExceptions as $exceptionClass) {
      if ($e instanceof $exceptionClass) {
        return TRUE;
      }
    }

    $temporaryMessages = [
      "connection",
      "timeout",
      "temporary",
      "retry",
      "busy",
      "locked",
    ];

    $message = strtolower($e->getMessage());
    foreach ($temporaryMessages as $keyword) {
      if (strpos($message, $keyword) !== FALSE) {
        return TRUE;
      }
    }

    return FALSE;
  }

  /**
   * Check if the exception represents a permanent failure.
   *
   * @param \Exception $e
   *   The exception to check.
   *
   * @return bool
   *   TRUE if the failure is permanent.
   */
  protected function isPermanentFailure(\Exception $e): bool {
    $permanentExceptions = [
      "InvalidArgumentException",
      "EntityNotFoundException",
    ];

    foreach ($permanentExceptions as $exceptionClass) {
      if ($e instanceof $exceptionClass) {
        return TRUE;
      }
    }

    $permanentMessages = [
      "not found",
      "invalid",
      "missing",
      "required",
    ];

    $message = strtolower($e->getMessage());
    foreach ($permanentMessages as $keyword) {
      if (strpos($message, $keyword) !== FALSE) {
        return TRUE;
      }
    }

    return FALSE;
  }

}
