<?php

declare(strict_types=1);

namespace Drupal\flowdrop_orchestration_connector;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\flowdrop_orchestration_connector\Service\InvokeTriggerService;
use Drupal\flowdrop_pipeline\Constants\PipelineStatus;
use Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface;
use Drupal\orchestration\Service;
use Drupal\orchestration\ServiceConfig;
use Drupal\orchestration\ServicesProviderInterface;
use Psr\Log\LoggerInterface;

/**
 * Provides FlowDrop workflows as services for the Orchestration module.
 *
 * This services provider exposes FlowDrop workflows that have orchestration
 * trigger nodes configured. External automation platforms (like Activepieces)
 * can discover and invoke these workflows via the Orchestration module's API.
 *
 * Each exposed service represents a workflow with an "orchestration.invoke"
 * trigger. When the service is called, the workflow is executed with the
 * provided payload data.
 *
 * Service discovery:
 * - GET /orchestration/services lists all available FlowDrop workflows
 *
 * Service execution:
 * - POST /orchestration/service/execute invokes a specific workflow
 *
 * @see \Drupal\orchestration\ServicesProviderInterface
 * @see \Drupal\flowdrop_orchestration_connector\Service\InvokeTriggerService
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class ServicesProvider implements ServicesProviderInterface {

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

  /**
   * {@inheritdoc}
   */
  public function getId(): string {
    return "flowdrop";
  }

  /**
   * {@inheritdoc}
   *
   * Returns all FlowDrop workflows with orchestration invoke triggers.
   * Each workflow is exposed as a callable service with configuration
   * for the payload schema defined in the trigger.
   */
  public function getAll(): array {
    $services = [];

    // Load all orchestration invoke triggers.
    $triggerConfigs = $this->invokeTriggerService->loadOrchestrationTriggers();

    foreach ($triggerConfigs as $triggerConfig) {
      $service = $this->buildService($triggerConfig);
      if ($service !== NULL) {
        $services[] = $service;
      }
    }

    return $services;
  }

  /**
   * {@inheritdoc}
   *
   * Execute a FlowDrop workflow via its orchestration trigger.
   *
   * @param \Drupal\orchestration\Service $service
   *   The service to execute.
   * @param array<string, mixed> $config
   *   The service configuration containing:
   *   - payload: The data to pass to the workflow trigger
   *   - Any other config fields defined in the trigger schema.
   *
   * @return array<string, mixed>|string
   *   The execution result.
   */
  public function execute(Service $service, array $config): array|string {
    $serviceId = $service->id();

    $this->logger->info("Executing FlowDrop service @service", [
      "@service" => $serviceId,
    ]);

    // Load the trigger configuration.
    $triggerConfig = $this->invokeTriggerService->loadTrigger($serviceId);

    if ($triggerConfig === NULL) {
      $this->logger->error("Trigger configuration not found: @id", [
        "@id" => $serviceId,
      ]);
      return [
        "error" => "Trigger configuration not found",
        "service_id" => $serviceId,
      ];
    }

    // Extract payload from config (external platform sends data in config).
    $payload = $config["payload"] ?? [];

    // Extract any additional metadata.
    $metadata = [
      "source" => "orchestration",
      "received_at" => time(),
    ];

    // Invoke the trigger.
    try {
      $result = $this->invokeTriggerService->invoke($triggerConfig, $payload, $metadata);
      return $result;
    }
    catch (\Exception $e) {
      $this->logger->error("FlowDrop service @service execution failed: @message", [
        "@service" => $serviceId,
        "@message" => $e->getMessage(),
      ]);

      return [
        "error" => $e->getMessage(),
        "service_id" => $serviceId,
        "status" => PipelineStatus::FAILED,
      ];
    }
  }

  /**
   * Build an Orchestration Service from a trigger configuration.
   *
   * @param \Drupal\flowdrop_trigger\FlowDropTriggerConfigInterface $triggerConfig
   *   The trigger configuration.
   *
   * @return \Drupal\orchestration\Service|null
   *   The service or NULL if the workflow is invalid.
   */
  protected function buildService(FlowDropTriggerConfigInterface $triggerConfig): ?Service {
    $configId = $triggerConfig->id();
    if ($configId === NULL) {
      return NULL;
    }

    // Get the workflow for label and description.
    $workflow = $this->invokeTriggerService->getWorkflow($triggerConfig);
    if ($workflow === NULL) {
      return NULL;
    }

    // Build service label from workflow label and trigger description.
    $workflowLabel = $workflow->getLabel();
    $triggerLabel = $triggerConfig->label();
    $label = $triggerLabel !== "" && $triggerLabel !== $workflowLabel
      ? "{$workflowLabel}: {$triggerLabel}"
      : $workflowLabel;

    // Build description.
    $workflowDescription = $workflow->getDescription();
    $description = $workflowDescription !== ""
      ? $workflowDescription
      : "Execute the {$workflowLabel} workflow";

    // Create the service.
    $service = new Service(
      $this,
      $configId,
      $label,
      $description,
    );

    // Add payload configuration field.
    // External platforms will populate this with the data to send.
    $service->addConfig(new ServiceConfig(
      key: "payload",
      label: "Payload",
      description: "Data to pass to the workflow. Structure depends on the workflow's input requirements.",
      required: FALSE,
      type: "object",
      isEditable: TRUE,
      defaultValue: [],
      weight: 0,
    ));

    // Get payload schema from trigger conditions if defined.
    $conditions = $triggerConfig->getConditions();
    $payloadSchema = $conditions["payload_schema"] ?? NULL;

    if (is_array($payloadSchema)) {
      // If the trigger defines a payload schema, add individual config fields
      // for each required property.
      $requiredFields = $conditions["required_fields"] ?? [];

      if (isset($payloadSchema["properties"]) && is_array($payloadSchema["properties"])) {
        $weight = 1;
        foreach ($payloadSchema["properties"] as $key => $schema) {
          $fieldType = $schema["type"] ?? "string";
          $fieldLabel = $schema["title"] ?? ucfirst((string) $key);
          $fieldDescription = $schema["description"] ?? "";
          $isRequired = in_array($key, $requiredFields, TRUE);
          $defaultValue = $schema["default"] ?? ($fieldType === "object" ? [] : "");

          $service->addConfig(new ServiceConfig(
            key: "payload.{$key}",
            label: $fieldLabel,
            description: $fieldDescription,
            required: $isRequired,
            type: $fieldType,
            isEditable: TRUE,
            defaultValue: $defaultValue,
            weight: $weight++,
          ));
        }
      }
    }

    return $service;
  }

}
