<?php

declare(strict_types=1);

namespace Drupal\flowdrop_runtime\Form\Trait;

use Drupal\Core\Form\FormStateInterface;
use Drupal\flowdrop_job\Constants\JobStatus;
use Drupal\flowdrop_orchestration\Service\Orchestrator\OrchestratorHelperInterface;
use Drupal\flowdrop_pipeline\Constants\PipelineStatus;
use Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface;

/**
 * Provides pipeline creation and execution logic for the execution form.
 *
 * Extracts methods for handling existing pipelines, creating new ones,
 * executing pipelines, and resetting pipeline/job states.
 *
 * Requirements for using this trait:
 * - The class must have an $entityTypeManager property
 *   of type EntityTypeManagerInterface
 * - The class must have a $jobGenerationService property
 *   of type JobGenerationService
 * - The class must have an $orchestratorHelper property
 *   of type OrchestratorHelperInterface
 * - The class must have a $synchronousPipelineOrchestrator property
 *   of type SynchronousPipelineOrchestrator
 * - The class must have an $asynchronousOrchestrator property
 *   of type AsynchronousOrchestrator
 * - The class must have a $messengerService property
 *   of type MessengerInterface
 * - The class must have a $logger property of type LoggerInterface
 * - The class must implement t() for translation
 *
 * @internal This trait is not part of the public API and may change without notice.
 */
trait WorkflowExecutionPipelineTrait {

  /**
   * Handles selecting an existing pipeline.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   *
   * @return \Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface|null
   *   The pipeline entity or NULL if not found.
   */
  protected function handleExistingPipeline(FormStateInterface $form_state): ?FlowDropPipelineInterface {
    $existing_pipeline_id = $form_state->getValue("existing_pipeline_id");
    $regenerate_jobs = (bool) $form_state->getValue("regenerate_jobs");
    $reset_pipeline = (bool) $form_state->getValue("reset_pipeline");

    $pipeline = $this->entityTypeManager
      ->getStorage("flowdrop_pipeline")
      ->load($existing_pipeline_id);

    if (!$pipeline instanceof FlowDropPipelineInterface) {
      return NULL;
    }

    // Regenerate jobs if requested.
    if ($regenerate_jobs) {
      $this->jobGenerationService->clearJobs($pipeline);
      $this->jobGenerationService->generateJobs($pipeline);
      $this->messengerService->addStatus($this->t("Jobs regenerated for pipeline '@label'.", [
        "@label" => $pipeline->label(),
      ]));
    }

    // Reset pipeline status if requested.
    if ($reset_pipeline) {
      $this->resetPipelineAndJobs($pipeline);
      $this->messengerService->addStatus($this->t("Pipeline '@label' reset to pending status.", [
        "@label" => $pipeline->label(),
      ]));
    }

    return $pipeline;
  }

  /**
   * Creates a new pipeline from a workflow.
   *
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param string $workflow_id
   *   The workflow ID.
   *
   * @return \Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface|null
   *   The created pipeline entity or NULL on failure.
   */
  protected function createNewPipeline(FormStateInterface $form_state, string $workflow_id): ?FlowDropPipelineInterface {
    $pipeline_label = $form_state->getValue("pipeline_label");
    $pipeline_description = $form_state->getValue("pipeline_description");
    $max_concurrent_jobs = (int) $form_state->getValue("max_concurrent_jobs", 5);
    $retry_strategy = $form_state->getValue("retry_strategy", "individual");

    // Create the pipeline.
    $pipeline_storage = $this->entityTypeManager->getStorage("flowdrop_pipeline");
    $pipeline = $pipeline_storage->create([
      "bundle" => "default",
      "label" => $pipeline_label,
      "description" => $pipeline_description,
      "workflow_id" => $workflow_id,
      "status" => PipelineStatus::PENDING,
      "max_concurrent_jobs" => $max_concurrent_jobs,
      "retry_strategy" => $retry_strategy,
    ]);

    $pipeline->save();

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

    // Generate jobs for the pipeline.
    if ($pipeline instanceof FlowDropPipelineInterface) {
      $jobs = $this->jobGenerationService->generateJobs($pipeline);
      $this->messengerService->addStatus($this->t("Generated @count jobs for the pipeline.", [
        "@count" => count($jobs),
      ]));

      return $pipeline;
    }

    return NULL;
  }

  /**
   * Executes a pipeline with the specified executor.
   *
   * @param \Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface $pipeline
   *   The pipeline to execute.
   * @param string $executor_id
   *   The executor ID (synchronous or asynchronous).
   */
  protected function executePipeline(FlowDropPipelineInterface $pipeline, string $executor_id): void {
    // Check if pipeline has jobs.
    $jobs = $pipeline->getJobs();
    if (empty($jobs)) {
      throw new \RuntimeException("Pipeline has no jobs. Job generation may have failed.");
    }

    $this->logger->info("Executing pipeline @pipeline_id with executor @executor_id", [
      "@pipeline_id" => $pipeline->id(),
      "@executor_id" => $executor_id,
    ]);

    if ($this->orchestratorHelper->isSynchronous($executor_id)) {
      // Execute pipeline synchronously.
      $response = $this->synchronousPipelineOrchestrator->executePipeline($pipeline);

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

      $this->messengerService->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->messengerService->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() === PipelineStatus::FAILED) {
        $this->messengerService->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->messengerService->addStatus($this->t("Pipeline '@label' has been queued for asynchronous execution.", [
          "@label" => $pipeline->label(),
        ]));

        $this->messengerService->addStatus($this->t("The pipeline will be processed in the background. Check the pipeline status page for updates."));
      }
      else {
        throw new \RuntimeException("Failed to queue pipeline for asynchronous execution.");
      }
    }
    else {
      throw new \RuntimeException(sprintf("Unknown executor type: %s", $executor_id));
    }

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

  /**
   * Resets the pipeline and all its jobs to pending status.
   *
   * @param \Drupal\flowdrop_pipeline\Entity\FlowDropPipelineInterface $pipeline
   *   The pipeline to reset.
   */
  protected function resetPipelineAndJobs(FlowDropPipelineInterface $pipeline): void {
    // Reset the pipeline status.
    $pipeline->setStatus(PipelineStatus::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(JobStatus::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(),
    ]);
  }

}
