<?php

declare(strict_types=1);

namespace Drupal\flowdrop_orchestration_connector\Service;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\flowdrop_orchestration\DTO\Orchestrator\OrchestrationRequest;
use Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorHelperInterface;
use Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface;
use Drupal\flowdrop_trigger\Service\EventTypePluginManager;
use Drupal\flowdrop_workflow\FlowDropWorkflowInterface;
use Psr\Log\LoggerInterface;

/**
 * Service to invoke FlowDrop workflows from external automation platforms.
 *
 * This service bridges the Orchestration module's API to FlowDrop's trigger
 * system. When an external platform calls a FlowDrop service via the
 * Orchestration module, this service:
 *
 * 1. Loads the trigger configuration for the requested workflow
 * 2. Builds the invocation context with the payload from the external platform
 * 3. Executes the workflow through FlowDrop's orchestrator system
 * 4. Returns the execution result to the external platform
 *
 * This approach ensures that:
 * - All workflow executions go through FlowDrop's standard pipeline/job system
 * - Trigger configurations (orchestrator settings, data mapping) are respected
 * - Execution history and logging work consistently
 *
 * @see \Drupal\flowdrop_orchestration_connector\ServicesProvider
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class InvokeTriggerService {

  /**
   * The event type ID for orchestration invocations.
   */
  public const EVENT_TYPE = "orchestration.invoke";

  /**
   * Constructs a new InvokeTriggerService.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorHelperInterface $orchestratorHelper
   *   The orchestrator helper service.
   * @param \Drupal\flowdrop_trigger\Service\EventTypePluginManager $eventTypeManager
   *   The event type plugin manager.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger service.
   */
  public function __construct(
    protected EntityTypeManagerInterface $entityTypeManager,
    protected OrchestratorHelperInterface $orchestratorHelper,
    protected EventTypePluginManager $eventTypeManager,
    protected LoggerInterface $logger,
  ) {}

  /**
   * Invoke a workflow trigger from an external platform.
   *
   * @param \Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface $triggerConfig
   *   The trigger configuration to invoke.
   * @param array<string, mixed> $payload
   *   The payload data from the external platform.
   * @param array<string, mixed> $metadata
   *   Optional metadata about the invocation.
   *
   * @return array<string, mixed>
   *   The execution result containing:
   *   - execution_id: Unique execution identifier
   *   - status: Execution status (completed, failed, pending)
   *   - results: Workflow output data
   *   - execution_time: Time taken in seconds
   *   - metadata: Additional execution metadata
   *
   * @throws \InvalidArgumentException
   *   If the workflow or trigger configuration is invalid.
   * @throws \RuntimeException
   *   If workflow execution fails.
   */
  public function invoke(
    FlowDropTriggerConfigInterface $triggerConfig,
    array $payload = [],
    array $metadata = [],
  ): array {
    $configId = $triggerConfig->id();
    if ($configId === NULL) {
      throw new \InvalidArgumentException("Trigger configuration has no ID");
    }

    $workflowId = $triggerConfig->getWorkflowId();
    $nodeId = $triggerConfig->getNodeId();

    $this->logger->info("Invoking workflow @workflow via orchestration trigger @trigger", [
      "@workflow" => $workflowId,
      "@trigger" => $configId,
    ]);

    // Load the workflow.
    $workflowStorage = $this->entityTypeManager->getStorage("flowdrop_workflow");
    /** @var \Drupal\flowdrop_workflow\FlowDropWorkflowInterface|null $workflow */
    $workflow = $workflowStorage->load($workflowId);

    if ($workflow === NULL) {
      throw new \InvalidArgumentException("Workflow not found: {$workflowId}");
    }

    if (!$workflow->status()) {
      throw new \InvalidArgumentException("Workflow is disabled: {$workflowId}");
    }

    // Build invocation context for the event type plugin.
    $invocationId = uniqid("orch_", TRUE);
    $context = [
      "invocation_id" => $invocationId,
      "service_id" => "flowdrop::{$configId}",
      "workflow_id" => $workflowId,
      "trigger_config_id" => $configId,
      "trigger_node_id" => $nodeId,
      "payload" => $payload,
      "metadata" => $metadata,
    ];

    // Get the event type plugin to extract trigger data.
    try {
      $eventTypePlugin = $this->eventTypeManager->createInstance(self::EVENT_TYPE);
    }
    catch (\Exception $e) {
      throw new \RuntimeException("Failed to load orchestration.invoke event type: " . $e->getMessage());
    }

    // Extract trigger data using the event type plugin.
    $triggerData = $eventTypePlugin->extractTriggerData(NULL, NULL, $context);

    // Build initial context with trigger data.
    // The trigger node ID is used to route data to the correct trigger node.
    $initialContext = [
      $nodeId => $triggerData,
      "trigger_node_id" => $nodeId,
      "trigger_config_id" => $configId,
    ];

    // Get orchestrator settings from trigger config.
    $orchestratorSettings = $triggerConfig->getOrchestratorSettings();
    $orchestratorType = $orchestratorSettings["type"] ?? OrchestratorHelperInterface::SYNCHRONOUS_PIPELINE;

    // Get the orchestrator instance with fallback handling.
    $orchestrator = $this->orchestratorHelper->getInstance(
      $orchestratorType,
      OrchestratorHelperInterface::SYNCHRONOUS_PIPELINE
    );

    // Build orchestration options.
    $options = [
      "orchestrator" => $orchestratorType,
      "trigger_config_id" => $configId,
      "invocation_id" => $invocationId,
      "source" => "orchestration",
    ];

    // Merge with orchestrator settings.
    if (isset($orchestratorSettings["pipeline_mode"])) {
      $options["pipeline_mode"] = $orchestratorSettings["pipeline_mode"];
    }
    if (isset($orchestratorSettings["priority"])) {
      $options["priority"] = $orchestratorSettings["priority"];
    }
    if (isset($orchestratorSettings["timeout"])) {
      $options["timeout"] = $orchestratorSettings["timeout"];
    }
    if (isset($orchestratorSettings["retry"])) {
      $options["retry"] = $orchestratorSettings["retry"];
    }

    // Generate pipeline ID.
    $pipelineId = "pipeline_{$workflowId}_{$invocationId}";

    // Create orchestration request.
    $request = new OrchestrationRequest(
      workflowId: $workflowId,
      pipelineId: $pipelineId,
      workflow: $this->workflowToArray($workflow),
      initialData: $initialContext,
      options: $options,
    );

    // Execute the workflow.
    try {
      $response = $orchestrator->orchestrate($request);

      $this->logger->info("Workflow @workflow executed successfully via orchestration: @status", [
        "@workflow" => $workflowId,
        "@status" => $response->getStatus(),
      ]);

      return $response->toArray();
    }
    catch (\Exception $e) {
      $this->logger->error("Workflow @workflow execution failed: @message", [
        "@workflow" => $workflowId,
        "@message" => $e->getMessage(),
      ]);

      // Return error response in same format as success.
      return [
        "execution_id" => $invocationId,
        "status" => "failed",
        "results" => [],
        "execution_time" => 0,
        "metadata" => [
          "error" => $e->getMessage(),
          "workflow_id" => $workflowId,
          "trigger_config_id" => $configId,
        ],
      ];
    }
  }

  /**
   * Load all trigger configurations for orchestration invokes.
   *
   * @return array<\Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface>
   *   Array of trigger configurations with event_type = orchestration.invoke.
   */
  public function loadOrchestrationTriggers(): array {
    $configStorage = $this->entityTypeManager->getStorage("flowdrop_trigger_config");
    $workflowStorage = $this->entityTypeManager->getStorage("flowdrop_workflow");

    /** @var \Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface[] $configs */
    $configs = $configStorage->loadByProperties([
      "event_type" => self::EVENT_TYPE,
      "status" => TRUE,
    ]);

    // Filter out configs with missing or disabled workflows.
    $validConfigs = [];
    foreach ($configs as $config) {
      $workflow = $workflowStorage->load($config->getWorkflowId());
      if ($workflow instanceof FlowDropWorkflowInterface && $workflow->status()) {
        $validConfigs[] = $config;
      }
    }

    return $validConfigs;
  }

  /**
   * Load a specific trigger configuration by ID.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   *
   * @return \Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface|null
   *   The trigger configuration or NULL if not found.
   */
  public function loadTrigger(string $triggerId): ?FlowDropTriggerConfigInterface {
    $configStorage = $this->entityTypeManager->getStorage("flowdrop_trigger_config");

    /** @var \Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface|null $config */
    $config = $configStorage->load($triggerId);

    if ($config === NULL) {
      return NULL;
    }

    // Verify it's an orchestration invoke trigger.
    if ($config->getEventType() !== self::EVENT_TYPE) {
      return NULL;
    }

    return $config;
  }

  /**
   * Get the workflow entity for a trigger.
   *
   * @param \Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface $triggerConfig
   *   The trigger configuration.
   *
   * @return \Drupal\flowdrop_workflow\FlowDropWorkflowInterface|null
   *   The workflow entity or NULL if not found.
   */
  public function getWorkflow(FlowDropTriggerConfigInterface $triggerConfig): ?FlowDropWorkflowInterface {
    $workflowStorage = $this->entityTypeManager->getStorage("flowdrop_workflow");

    /** @var \Drupal\flowdrop_workflow\FlowDropWorkflowInterface|null $workflow */
    $workflow = $workflowStorage->load($triggerConfig->getWorkflowId());

    return $workflow;
  }

  /**
   * Convert a workflow entity to array format for orchestration.
   *
   * @param \Drupal\flowdrop_workflow\FlowDropWorkflowInterface $workflow
   *   The workflow entity.
   *
   * @return array<string, mixed>
   *   The workflow as an array.
   */
  protected function workflowToArray(FlowDropWorkflowInterface $workflow): array {
    return [
      "id" => $workflow->id(),
      "label" => $workflow->getLabel(),
      "description" => $workflow->getDescription(),
      "nodes" => $workflow->getNodes(),
      "edges" => $workflow->getEdges(),
      "metadata" => $workflow->getMetadata(),
    ];
  }

}
