<?php

declare(strict_types=1);

namespace Drupal\flowdrop_orchestration_connector\EventSubscriber;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\flowdrop_runtime\Constants\ExecutionStatus;
use Drupal\flowdrop_runtime\Event\RealTimeBroadcastEvent;
use Drupal\orchestration\Webhooks;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Event subscriber for FlowDrop pipeline completion events.
 *
 * Listens to FlowDrop's real-time events and dispatches webhooks to registered
 * external platforms when workflows complete. This enables external automation
 * platforms to receive notifications about FlowDrop workflow execution results.
 *
 * Webhook registration:
 * - Platforms register webhooks via POST /orchestration/webhook/register
 * - Webhooks identified by ID (e.g., "flowdrop::my_workflow__node_abc")
 * - When a workflow completes, this subscriber dispatches to matching webhooks
 *
 * @see \Drupal\orchestration\Webhooks
 * @see \Drupal\flowdrop_runtime\Service\RealTime\EventBroadcaster
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class PipelineEventSubscriber implements EventSubscriberInterface {

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

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      RealTimeBroadcastEvent::NAME => ["onRealTimeEvent", 0],
    ];
  }

  /**
   * Handle FlowDrop real-time events.
   *
   * @param \Drupal\flowdrop_runtime\Event\RealTimeBroadcastEvent $event
   *   The event containing real-time event data.
   */
  public function onRealTimeEvent(RealTimeBroadcastEvent $event): void {
    $type = $event->type;
    $eventName = $event->event;
    $executionId = $event->executionId;
    $data = $event->data;

    // Only process execution-level completion events.
    if ($type !== "execution") {
      return;
    }

    // Handle completion events (completed, failed).
    if ($eventName === ExecutionStatus::COMPLETED || $eventName === ExecutionStatus::FAILED) {
      $this->dispatchWebhook($executionId, $eventName, $data);
    }
  }

  /**
   * Dispatch a webhook for pipeline completion.
   *
   * @param string $executionId
   *   The execution ID (usually pipeline ID).
   * @param string $status
   *   The completion status (completed, failed).
   * @param array<string, mixed> $data
   *   Additional event data.
   */
  protected function dispatchWebhook(string $executionId, string $status, array $data): void {
    // Try to load the pipeline to get workflow and trigger info.
    $pipelineStorage = $this->entityTypeManager->getStorage("flowdrop_pipeline");

    // Extract pipeline ID from execution ID if it follows our format.
    $pipelineId = $this->extractPipelineId($executionId);
    if ($pipelineId === NULL) {
      return;
    }

    $pipeline = $pipelineStorage->load($pipelineId);
    if ($pipeline === NULL) {
      return;
    }

    // Get workflow ID for webhook routing.
    /** @var \Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface $pipeline */
    $workflowId = $pipeline->getWorkflowId();

    // Check if this pipeline was triggered via orchestration.
    // Look for orchestration source in execution context or input data.
    $inputData = $pipeline->getInputData();
    $triggerConfigId = $inputData["trigger_config_id"] ?? NULL;

    if ($triggerConfigId === NULL) {
      // Not an orchestration-triggered pipeline.
      return;
    }

    // Build webhook ID - matches what external platforms register.
    $webhookId = "flowdrop::{$triggerConfigId}";

    // Build payload for the webhook.
    $payload = [
      "event" => "workflow.{$status}",
      "timestamp" => time(),
      "execution_id" => $executionId,
      "pipeline_id" => $pipelineId,
      "workflow_id" => $workflowId,
      "trigger_config_id" => $triggerConfigId,
      "status" => $status,
      "output" => $pipeline->getOutputData(),
      "error" => $status === ExecutionStatus::FAILED ? ($pipeline->getErrorMessage() ?: ($data["error"] ?? NULL)) : NULL,
      "execution_time" => $data["execution_time"] ?? NULL,
    ];

    $this->logger->info("Dispatching webhook @webhook for pipeline @pipeline (@status)", [
      "@webhook" => $webhookId,
      "@pipeline" => $pipelineId,
      "@status" => $status,
    ]);

    // Dispatch to the registered webhook URL.
    $this->webhooks->dispatch($webhookId, $payload);
  }

  /**
   * Extract pipeline ID from execution ID.
   *
   * Execution IDs may follow different formats. This method attempts to
   * extract the pipeline ID from common patterns.
   *
   * @param string $executionId
   *   The execution ID.
   *
   * @return string|null
   *   The pipeline ID or NULL if not extractable.
   */
  protected function extractPipelineId(string $executionId): ?string {
    // Pattern: pipeline_workflowid_timestamp or similar.
    // If the execution ID is numeric, it's likely the pipeline ID directly.
    if (is_numeric($executionId)) {
      return $executionId;
    }

    // Try to extract from pattern: pipeline_X_Y or exec_X.
    if (preg_match("/^pipeline_(\d+)/", $executionId, $matches)) {
      return $matches[1];
    }

    // The execution ID might be the pipeline ID itself.
    // Check if it exists in the database.
    return $executionId;
  }

}
