<?php

declare(strict_types=1);

namespace Drupal\flowdrop_orchestration_connector\EventSubscriber;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\orchestration\Event\PollEventId;
use Drupal\orchestration\Event\PollEventTimestamp;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Event subscriber for orchestration poll events.
 *
 * Allows external automation platforms to poll for FlowDrop pipeline
 * execution results. Platforms can poll by:
 * - Timestamp: Get all pipelines completed since a specific time
 * - ID: Get all pipelines completed after a specific pipeline ID.
 *
 * Poll request format (POST /orchestration/poll):
 * - By timestamp: { "name": "flowdrop_pipelines", "timestamp": 1234567890 }
 * - By ID: { "name": "flowdrop_pipelines", "id": "pipeline_123" }
 *
 * Response format:
 * - timestamp-based: [{ "timestamp": 123, "data": {...} }, ...]
 * - ID-based: [{ "id": "pipeline_124", "data": {...} }, ...]
 *
 * @see \Drupal\orchestration\Controller\Connect::poll()
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class PollEventSubscriber implements EventSubscriberInterface {

  /**
   * The poll event name for FlowDrop pipelines.
   */
  public const POLL_NAME = "flowdrop_pipelines";

  /**
   * Constructs a new PollEventSubscriber.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger service.
   */
  public function __construct(
    protected EntityTypeManagerInterface $entityTypeManager,
    protected LoggerInterface $logger,
  ) {}

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      "orchestration_poll.timestamp" => ["onPollTimestamp", 0],
      "orchestration_poll.id" => ["onPollId", 0],
    ];
  }

  /**
   * Handle timestamp-based poll events.
   *
   * Returns all completed pipelines since the given timestamp.
   *
   * @param \Drupal\orchestration\Event\PollEventTimestamp $event
   *   The poll event.
   */
  public function onPollTimestamp(PollEventTimestamp $event): void {
    // Only respond to our poll name.
    if ($event->getWildcard() !== self::POLL_NAME) {
      return;
    }

    $timestamp = $event->getTimestamp();

    $this->logger->debug("Poll by timestamp @timestamp for FlowDrop pipelines", [
      "@timestamp" => $timestamp,
    ]);

    try {
      $pipelineStorage = $this->entityTypeManager->getStorage("flowdrop_pipeline");

      // Query pipelines completed after the given timestamp.
      // We use the "completed" field which stores the completion timestamp.
      $query = $pipelineStorage->getQuery()
        ->condition("completed", $timestamp, ">")
        ->condition("status", ["completed", "failed"], "IN")
        ->sort("completed", "ASC")
        // Access bypassed: background polling runs without user context.
        ->accessCheck(FALSE);

      $ids = $query->execute();

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

      /** @var \Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface[] $pipelines */
      $pipelines = $pipelineStorage->loadMultiple($ids);

      foreach ($pipelines as $pipeline) {
        // Only include orchestration-triggered pipelines.
        $inputData = $pipeline->getInputData();
        if (!isset($inputData["trigger_config_id"])) {
          continue;
        }

        $completedTimestamp = $pipeline->getCompleted();
        $data = $this->buildPipelineData($pipeline);

        $event->addItem($completedTimestamp, $data);
      }
    }
    catch (\Exception $e) {
      $this->logger->error("Poll by timestamp failed: @message", [
        "@message" => $e->getMessage(),
      ]);
    }
  }

  /**
   * Handle ID-based poll events.
   *
   * Returns all completed pipelines after the given pipeline ID.
   *
   * @param \Drupal\orchestration\Event\PollEventId $event
   *   The poll event.
   */
  public function onPollId(PollEventId $event): void {
    // Only respond to our poll name.
    if ($event->getWildcard() !== self::POLL_NAME) {
      return;
    }

    $lastId = $event->getId();

    $this->logger->debug("Poll by ID @id for FlowDrop pipelines", [
      "@id" => $lastId,
    ]);

    try {
      $pipelineStorage = $this->entityTypeManager->getStorage("flowdrop_pipeline");

      // Query pipelines with ID greater than the last seen ID.
      // Note: This assumes numeric IDs and may need adjustment for
      // UUID-based systems.
      $query = $pipelineStorage->getQuery()
        ->condition("status", ["completed", "failed"], "IN")
        ->sort("id", "ASC")
        // Access bypassed: background polling runs without user context.
        ->accessCheck(FALSE);

      // If lastId is provided and numeric, filter by it.
      if ($lastId !== "" && is_numeric($lastId)) {
        $query->condition("id", (int) $lastId, ">");
      }

      $ids = $query->execute();

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

      /** @var \Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface[] $pipelines */
      $pipelines = $pipelineStorage->loadMultiple($ids);

      foreach ($pipelines as $pipeline) {
        // Only include orchestration-triggered pipelines.
        $inputData = $pipeline->getInputData();
        if (!isset($inputData["trigger_config_id"])) {
          continue;
        }

        $pipelineId = $pipeline->id();
        if ($pipelineId === NULL) {
          continue;
        }

        $data = $this->buildPipelineData($pipeline);

        $event->addItem($pipelineId, $data);
      }
    }
    catch (\Exception $e) {
      $this->logger->error("Poll by ID failed: @message", [
        "@message" => $e->getMessage(),
      ]);
    }
  }

  /**
   * Build pipeline data for poll response.
   *
   * @param \Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface $pipeline
   *   The pipeline entity.
   *
   * @return array<string, mixed>
   *   The pipeline data array.
   */
  protected function buildPipelineData(object $pipeline): array {
    $inputData = $pipeline->getInputData();

    return [
      "pipeline_id" => $pipeline->id(),
      "workflow_id" => $pipeline->getWorkflowId(),
      "trigger_config_id" => $inputData["trigger_config_id"] ?? NULL,
      "status" => $pipeline->getStatus(),
      "started" => $pipeline->getStarted(),
      "completed" => $pipeline->getCompleted(),
      "input_data" => $inputData["payload"] ?? [],
      "output_data" => $pipeline->getOutputData(),
      "error_message" => $pipeline->getErrorMessage() ?: NULL,
    ];
  }

}
