<?php

declare(strict_types=1);

namespace Drupal\flowdrop_runtime\Form;

use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface;
use Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorHelperInterface;
use Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorPluginManager;
use Drupal\flowdrop_runtime\Service\Orchestrator\AsynchronousOrchestrator;
use Drupal\flowdrop_runtime\Service\Orchestrator\SynchronousPipelineOrchestrator;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Form for selecting and executing pipelines with a chosen executor.
 *
 * Provides a user interface for:
 * - Selecting an existing pipeline from the system
 * - Choosing an orchestrator/executor type (synchronous or asynchronous)
 * - Executing the selected pipeline with the chosen orchestrator.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
final class PipelineExecutionForm extends FormBase {

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

  /**
   * The orchestrator helper service.
   *
   * @var \Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorHelperInterface
   */
  protected OrchestratorHelperInterface $orchestratorHelper;

  /**
   * The orchestrator plugin manager.
   *
   * @var \Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorPluginManager
   */
  protected OrchestratorPluginManager $orchestratorManager;

  /**
   * The synchronous pipeline orchestrator service.
   *
   * @var \Drupal\flowdrop_runtime\Service\Orchestrator\SynchronousPipelineOrchestrator
   */
  protected SynchronousPipelineOrchestrator $synchronousPipelineOrchestrator;

  /**
   * The asynchronous orchestrator service.
   *
   * @var \Drupal\flowdrop_runtime\Service\Orchestrator\AsynchronousOrchestrator
   */
  protected AsynchronousOrchestrator $asynchronousOrchestrator;

  /**
   * The logger service.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected LoggerInterface $logger;

  /**
   * Constructs a new PipelineExecutionForm.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorHelperInterface $orchestrator_helper
   *   The orchestrator helper service.
   * @param \Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorPluginManager $orchestrator_manager
   *   The orchestrator plugin manager (for definitions/descriptions).
   * @param \Drupal\flowdrop_runtime\Service\Orchestrator\SynchronousPipelineOrchestrator $synchronous_pipeline_orchestrator
   *   The synchronous pipeline orchestrator service.
   * @param \Drupal\flowdrop_runtime\Service\Orchestrator\AsynchronousOrchestrator $asynchronous_orchestrator
   *   The asynchronous orchestrator service.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger service.
   */
  public function __construct(
    EntityTypeManagerInterface $entity_type_manager,
    OrchestratorHelperInterface $orchestrator_helper,
    OrchestratorPluginManager $orchestrator_manager,
    SynchronousPipelineOrchestrator $synchronous_pipeline_orchestrator,
    AsynchronousOrchestrator $asynchronous_orchestrator,
    LoggerInterface $logger,
  ) {
    $this->entityTypeManager = $entity_type_manager;
    $this->orchestratorHelper = $orchestrator_helper;
    $this->orchestratorManager = $orchestrator_manager;
    $this->synchronousPipelineOrchestrator = $synchronous_pipeline_orchestrator;
    $this->asynchronousOrchestrator = $asynchronous_orchestrator;
    $this->logger = $logger;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): static {
    return new static(
      $container->get("entity_type.manager"),
      $container->get("flowdrop_orchestration.helper"),
      $container->get("flowdrop_orchestration.plugin_manager"),
      $container->get("flowdrop_runtime.synchronous_pipeline_orchestrator"),
      $container->get("flowdrop_runtime.asynchronous_orchestrator"),
      $container->get("logger.factory")->get("flowdrop_runtime")
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return "flowdrop_runtime_pipeline_execution_form";
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {
    // Add a description fieldset.
    $form["description"] = [
      "#type" => "markup",
      "#markup" => "<p>" . $this->t("Use this form to select and execute an existing pipeline with your chosen executor type.") . "</p>",
    ];

    // Pipeline selection fieldset.
    $form["pipeline_selection"] = [
      "#type" => "fieldset",
      "#title" => $this->t("Pipeline Selection"),
      "#description" => $this->t("Choose a pipeline to execute."),
    ];

    // Build pipeline options.
    $pipeline_options = $this->buildPipelineOptions();

    if (empty($pipeline_options)) {
      $form["pipeline_selection"]["no_pipelines"] = [
        "#type" => "markup",
        "#markup" => "<p><em>" . $this->t("No pipelines are available. Please create a pipeline first.") . "</em></p>",
      ];

      return $form;
    }

    $form["pipeline_selection"]["pipeline_id"] = [
      "#type" => "select",
      "#title" => $this->t("Pipeline"),
      "#description" => $this->t("Select the pipeline you want to execute."),
      "#options" => $pipeline_options,
      "#empty_option" => $this->t("- Select a pipeline -"),
      "#required" => TRUE,
      "#ajax" => [
        "callback" => "::pipelineSelectedCallback",
        "wrapper" => "pipeline-info-wrapper",
        "event" => "change",
      ],
    ];

    // Pipeline info container (populated via AJAX when pipeline is selected).
    $form["pipeline_info"] = [
      "#type" => "container",
      "#prefix" => '<div id="pipeline-info-wrapper">',
      "#suffix" => '</div>',
    ];

    // Show pipeline details if a pipeline is selected.
    $selected_pipeline_id = $form_state->getValue("pipeline_id");
    if ($selected_pipeline_id) {
      $this->buildPipelineInfoSection($form, $selected_pipeline_id);
    }

    // Executor selection fieldset.
    $form["executor_selection"] = [
      "#type" => "fieldset",
      "#title" => $this->t("Executor Selection"),
      "#description" => $this->t("Choose how to execute the pipeline."),
    ];

    // Build executor options from the orchestrator helper.
    $executor_options = $this->orchestratorHelper->getFormOptions();

    $form["executor_selection"]["executor_id"] = [
      "#type" => "radios",
      "#title" => $this->t("Executor Type"),
      "#description" => $this->t("Choose the orchestrator to use for execution."),
      "#options" => $executor_options,
      "#default_value" => OrchestratorHelperInterface::SYNCHRONOUS_PIPELINE,
      "#required" => TRUE,
    ];

    // Executor descriptions.
    $form["executor_selection"]["executor_descriptions"] = [
      "#type" => "details",
      "#title" => $this->t("Executor Details"),
      "#open" => FALSE,
    ];

    $definitions = $this->orchestratorManager->getDefinitions();
    $descriptions = [];
    foreach ($definitions as $id => $definition) {
      $descriptions[] = sprintf(
        "<strong>%s</strong>: %s",
        Html::escape($definition["label"]),
        Html::escape($definition["description"])
      );
    }

    $form["executor_selection"]["executor_descriptions"]["content"] = [
      "#type" => "markup",
      "#markup" => "<ul><li>" . implode("</li><li>", $descriptions) . "</li></ul>",
    ];

    // Execution options fieldset.
    $form["execution_options"] = [
      "#type" => "fieldset",
      "#title" => $this->t("Execution Options"),
      "#description" => $this->t("Configure additional execution options."),
    ];

    $form["execution_options"]["reset_jobs"] = [
      "#type" => "checkbox",
      "#title" => $this->t("Reset jobs and pipeline status before execution"),
      "#description" => $this->t("If checked, all jobs will be reset to 'pending' status and the pipeline will be reset before execution. This allows re-running a previously executed pipeline."),
      "#default_value" => FALSE,
    ];

    // Input data section (optional).
    $form["input_data"] = [
      "#type" => "details",
      "#title" => $this->t("Initial Input Data (Optional)"),
      "#description" => $this->t("Provide optional JSON-formatted input data for the pipeline execution."),
      "#open" => FALSE,
    ];

    $form["input_data"]["initial_data"] = [
      "#type" => "textarea",
      "#title" => $this->t("Input Data (JSON)"),
      "#description" => $this->t("Enter valid JSON data to pass as initial input to the pipeline. Leave empty for no initial data."),
      "#default_value" => "{}",
      "#rows" => 5,
    ];

    // Submit button.
    $form["actions"] = [
      "#type" => "actions",
    ];

    $form["actions"]["submit"] = [
      "#type" => "submit",
      "#value" => $this->t("Execute Pipeline"),
      "#button_type" => "primary",
    ];

    return $form;
  }

  /**
   * AJAX callback for pipeline selection.
   *
   * @param array<string, mixed> $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return array<string, mixed>
   *   The pipeline info section.
   */
  public function pipelineSelectedCallback(array &$form, FormStateInterface $form_state): array {
    return $form["pipeline_info"];
  }

  /**
   * Builds the pipeline options for the select element.
   *
   * @return array<string, string>
   *   An array of pipeline options keyed by pipeline ID.
   */
  protected function buildPipelineOptions(): array {
    $options = [];

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

      foreach ($pipelines as $pipeline) {
        if ($pipeline instanceof FlowDropPipelineInterface) {
          $job_counts = $pipeline->calculateJobCounts();
          $status = $pipeline->getStatus();

          // Build a descriptive label.
          $label = sprintf(
            "%s [%s] - %d jobs (%d pending, %d completed, %d failed)",
            $pipeline->label(),
            ucfirst($status),
            $job_counts["total"],
            $job_counts["pending"],
            $job_counts["completed"],
            $job_counts["failed"]
          );

          $pipeline_id = $pipeline->id();
          if (is_string($pipeline_id) || is_int($pipeline_id)) {
            $options[(string) $pipeline_id] = $label;
          }
        }
      }
    }
    catch (\Exception $e) {
      $this->logger->error("Failed to load pipelines: @message", [
        "@message" => $e->getMessage(),
      ]);
    }

    return $options;
  }

  /**
   * Builds the pipeline info section for display.
   *
   * @param array<string, mixed> $form
   *   The form array (passed by reference).
   * @param string $pipeline_id
   *   The selected pipeline ID.
   */
  protected function buildPipelineInfoSection(array &$form, string $pipeline_id): void {
    $pipeline = $this->entityTypeManager
      ->getStorage("flowdrop_pipeline")
      ->load($pipeline_id);

    if (!$pipeline instanceof FlowDropPipelineInterface) {
      $form["pipeline_info"]["error"] = [
        "#type" => "markup",
        "#markup" => "<p class='messages messages--warning'>" . $this->t("Selected pipeline could not be loaded.") . "</p>",
      ];
      return;
    }

    $job_counts = $pipeline->calculateJobCounts();
    $status = $pipeline->getStatus();

    $form["pipeline_info"]["details"] = [
      "#type" => "details",
      "#title" => $this->t("Pipeline Details: @label", ["@label" => $pipeline->label()]),
      "#open" => TRUE,
    ];

    $form["pipeline_info"]["details"]["info_table"] = [
      "#type" => "table",
      "#header" => [$this->t("Property"), $this->t("Value")],
      "#rows" => [
        [$this->t("ID"), $pipeline->id()],
        [$this->t("Label"), $pipeline->label()],
        [$this->t("Status"), ucfirst($status)],
        [$this->t("Workflow ID"), $pipeline->getWorkflowId()],
        [$this->t("Total Jobs"), $job_counts["total"]],
        [$this->t("Pending Jobs"), $job_counts["pending"]],
        [$this->t("Running Jobs"), $job_counts["running"]],
        [$this->t("Completed Jobs"), $job_counts["completed"]],
        [$this->t("Failed Jobs"), $job_counts["failed"]],
        [$this->t("Max Concurrent Jobs"), $pipeline->getMaxConcurrentJobs()],
        [$this->t("Retry Strategy"), ucfirst($pipeline->getRetryStrategy())],
      ],
    ];

    // Show warning for pipelines in certain states.
    if (in_array($status, ["running", "paused"], TRUE)) {
      $form["pipeline_info"]["warning"] = [
        "#type" => "markup",
        "#markup" => "<p class='messages messages--warning'>" .
        $this->t("This pipeline is currently @status. Executing it may cause unexpected behavior.", ["@status" => $status]) .
        "</p>",
      ];
    }

    // Show error message if pipeline has one.
    $error_message = $pipeline->getErrorMessage();
    if (!empty($error_message)) {
      $form["pipeline_info"]["error_message"] = [
        "#type" => "details",
        "#title" => $this->t("Previous Error"),
        "#open" => TRUE,
        "message" => [
          "#type" => "textarea",
          "#value" => $error_message,
          "#disabled" => TRUE,
          "#rows" => 3,
        ],
      ];
    }
  }

  /**
   * {@inheritdoc}
   */
  public function validateForm(array &$form, FormStateInterface $form_state): void {
    parent::validateForm($form, $form_state);

    // Validate pipeline selection.
    $pipeline_id = $form_state->getValue("pipeline_id");
    if (empty($pipeline_id)) {
      $form_state->setErrorByName("pipeline_id", $this->t("Please select a pipeline."));
      return;
    }

    // Validate pipeline exists.
    $pipeline = $this->entityTypeManager
      ->getStorage("flowdrop_pipeline")
      ->load($pipeline_id);

    if (!$pipeline instanceof FlowDropPipelineInterface) {
      $form_state->setErrorByName("pipeline_id", $this->t("The selected pipeline does not exist."));
      return;
    }

    // Validate executor selection.
    $executor_id = $form_state->getValue("executor_id");
    if (empty($executor_id)) {
      $form_state->setErrorByName("executor_id", $this->t("Please select an executor type."));
      return;
    }

    // Validate executor exists.
    if (!$this->orchestratorManager->hasDefinition($executor_id)) {
      $form_state->setErrorByName("executor_id", $this->t("The selected executor type is not available."));
      return;
    }

    // Validate input data JSON.
    $initial_data = $form_state->getValue("initial_data");
    if (!empty($initial_data) && $initial_data !== "{}") {
      $decoded = json_decode($initial_data, TRUE);
      if (json_last_error() !== JSON_ERROR_NONE) {
        $form_state->setErrorByName("initial_data", $this->t("The input data must be valid JSON. Error: @error", [
          "@error" => json_last_error_msg(),
        ]));
      }
    }

    // Check if pipeline has jobs.
    $jobs = $pipeline->getJobs();
    if (empty($jobs)) {
      $form_state->setErrorByName("pipeline_id", $this->t("The selected pipeline has no jobs. Please generate jobs first before executing."));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $pipeline_id = $form_state->getValue("pipeline_id");
    $executor_id = $form_state->getValue("executor_id");
    $initial_data_json = $form_state->getValue("initial_data");
    $reset_jobs = (bool) $form_state->getValue("reset_jobs");

    // Parse initial data.
    $initial_data = [];
    if (!empty($initial_data_json) && $initial_data_json !== "{}") {
      $decoded = json_decode($initial_data_json, TRUE);
      if (is_array($decoded)) {
        $initial_data = $decoded;
      }
    }

    // Load the pipeline.
    $pipeline = $this->entityTypeManager
      ->getStorage("flowdrop_pipeline")
      ->load($pipeline_id);

    if (!$pipeline instanceof FlowDropPipelineInterface) {
      $this->messenger()->addError($this->t("Failed to load the selected pipeline."));
      return;
    }

    // Reset jobs and pipeline if requested.
    if ($reset_jobs) {
      $this->resetPipelineAndJobs($pipeline);
      $this->messenger()->addStatus($this->t("Pipeline and jobs have been reset to pending status."));
    }

    $this->logger->info("Executing pipeline @pipeline_id with executor @executor_id (reset: @reset)", [
      "@pipeline_id" => $pipeline_id,
      "@executor_id" => $executor_id,
      "@reset" => $reset_jobs ? "yes" : "no",
    ]);

    try {
      // Execute based on executor type using helper methods to avoid
      // hardcoding original IDs that could change or collide.
      if ($this->orchestratorHelper->isSynchronous($executor_id)) {
        // Both synchronous and synchronous_pipeline use the synchronous
        // pipeline orchestrator for executing existing pipelines.
        $response = $this->synchronousPipelineOrchestrator->executePipeline($pipeline);

        $this->messenger()->addStatus($this->t("Pipeline '@label' executed successfully with @executor executor.", [
          "@label" => $pipeline->label(),
          "@executor" => $executor_id,
        ]));

        $this->messenger()->addStatus($this->t("Execution completed in @time seconds. Status: @status", [
          "@time" => round($response->getExecutionTime(), 3),
          "@status" => $response->getStatus(),
        ]));

        // Show additional details.
        $metadata = $response->getMetadata();
        if (isset($metadata["executed_jobs"]) && is_array($metadata["executed_jobs"])) {
          $this->messenger()->addStatus($this->t("Executed @count jobs in @iterations iterations.", [
            "@count" => count($metadata["executed_jobs"]),
            "@iterations" => $metadata["iterations"] ?? 1,
          ]));
        }

        // Handle warnings for partial failures.
        if ($response->getStatus() === "failed") {
          $this->messenger()->addWarning($this->t("Some jobs may have failed during execution. Check the pipeline details for more information."));
        }
      }
      elseif ($executor_id === OrchestratorHelperInterface::ASYNCHRONOUS) {
        // Execute pipeline asynchronously.
        $result = $this->asynchronousOrchestrator->startPipeline($pipeline);

        if ($result) {
          $this->messenger()->addStatus($this->t("Pipeline '@label' has been queued for asynchronous execution.", [
            "@label" => $pipeline->label(),
          ]));

          $this->messenger()->addStatus($this->t("The pipeline will be processed in the background. Check the pipeline status page for updates."));
        }
        else {
          $this->messenger()->addError($this->t("Failed to queue pipeline for asynchronous execution."));
        }
      }
      elseif ($this->orchestratorHelper->isStateGraph($executor_id)) {
        // StateGraph orchestrator - use synchronous pipeline orchestrator for
        // existing pipeline execution as StateGraph is primarily for workflow
        // orchestration, not re-executing existing pipelines.
        $response = $this->synchronousPipelineOrchestrator->executePipeline($pipeline);

        $this->messenger()->addStatus($this->t("Pipeline '@label' executed successfully.", [
          "@label" => $pipeline->label(),
        ]));

        $this->messenger()->addStatus($this->t("Execution completed in @time seconds. Status: @status", [
          "@time" => round($response->getExecutionTime(), 3),
          "@status" => $response->getStatus(),
        ]));

        // Show additional details.
        $metadata = $response->getMetadata();
        if (isset($metadata["executed_jobs"]) && is_array($metadata["executed_jobs"])) {
          $this->messenger()->addStatus($this->t("Executed @count jobs in @iterations iterations.", [
            "@count" => count($metadata["executed_jobs"]),
            "@iterations" => $metadata["iterations"] ?? 1,
          ]));
        }

        // Handle warnings for partial failures.
        if ($response->getStatus() === "failed") {
          $this->messenger()->addWarning($this->t("Some jobs may have failed during execution. Check the pipeline details for more information."));
        }
      }
      else {
        // Unknown orchestrator type - log error.
        $this->messenger()->addError($this->t("Unknown executor type: @executor", [
          "@executor" => $executor_id,
        ]));

        $this->logger->error("Unknown executor type @executor_id for pipeline @pipeline_id", [
          "@executor_id" => $executor_id,
          "@pipeline_id" => $pipeline_id,
        ]);

        return;
      }

      $this->logger->info("Pipeline @pipeline_id execution completed with executor @executor_id", [
        "@pipeline_id" => $pipeline_id,
        "@executor_id" => $executor_id,
      ]);

    }
    catch (\Exception $e) {
      $this->messenger()->addError($this->t("Pipeline execution failed: @message", [
        "@message" => $e->getMessage(),
      ]));

      $this->logger->error("Pipeline @pipeline_id execution failed: @message", [
        "@pipeline_id" => $pipeline_id,
        "@message" => $e->getMessage(),
        "@trace" => $e->getTraceAsString(),
      ]);
    }
  }

  /**
   * Resets the pipeline and all its jobs to pending status.
   *
   * This method:
   * - Resets the pipeline status to 'pending'
   * - Clears pipeline timestamps (started, completed)
   * - Clears pipeline error messages
   * - Resets all jobs to 'pending' status
   * - Clears job timestamps and error messages
   * - Resets job retry counts.
   *
   * @param \Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface $pipeline
   *   The pipeline to reset.
   */
  protected function resetPipelineAndJobs(FlowDropPipelineInterface $pipeline): void {
    // Reset the pipeline status.
    $pipeline->setStatus("pending");
    $pipeline->setStarted(0);
    $pipeline->setCompleted(0);
    $pipeline->setErrorMessage("");
    $pipeline->save();

    $this->logger->info("Pipeline @pipeline_id reset to pending status", [
      "@pipeline_id" => $pipeline->id(),
    ]);

    // Reset all jobs to pending.
    $jobs = $pipeline->getJobs();
    $reset_count = 0;

    foreach ($jobs as $job) {
      $job->setStatus("pending");
      $job->setStarted(0);
      $job->setCompleted(0);
      $job->setErrorMessage("");
      $job->setRetryCount(0);
      $job->save();
      $reset_count++;
    }

    $this->logger->info("Reset @count jobs to pending status for pipeline @pipeline_id", [
      "@count" => $reset_count,
      "@pipeline_id" => $pipeline->id(),
    ]);
  }

}
