<?php

declare(strict_types=1);

namespace Drupal\flowdrop_workflow\Controller\Api;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityMalformedException;
use Drupal\flowdrop\Controller\Api\ApiResponseTrait;
use Drupal\flowdrop_workflow\DTO\BundleImportResult;
use Drupal\flowdrop_workflow\Service\BundleExporter;
use Drupal\flowdrop_workflow\Service\BundleImporter;
use Drupal\flowdrop_workflow\Service\WorkflowValidatorInterface;
use Drupal\flowdrop_workflow\WorkflowDefinitionInterface;
use Drupal\flowdrop_node_type\Service\NodeMetadataResolver;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * API controller for workflow management.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
final class WorkflowsController extends ControllerBase {

  use ApiResponseTrait;

  /**
   * Constructs a WorkflowsController.
   */
  public function __construct(
    private readonly Connection $database,
    private readonly WorkflowValidatorInterface $validator,
    private readonly BundleImporter $bundleImporter,
    private readonly BundleExporter $bundleExporter,
    private readonly NodeMetadataResolver $metadataResolver,
  ) {}

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): static {
    return new static(
      $container->get('database'),
      $container->get('flowdrop_workflow.validator'),
      $container->get('flowdrop_workflow.bundle_importer'),
      $container->get('flowdrop_workflow.bundle_exporter'),
      $container->get('flowdrop_node_type.metadata_resolver'),
    );
  }

  /**
   * Get all workflows.
   */
  public function getWorkflows(Request $request): JsonResponse {
    try {
      // Get query parameters.
      $search = $request->query->get('search');
      if ($search) {
        $search = $this->database->escapeLike($search);
      }
      $limit = (int) ($request->query->get('limit') ?? 50);
      $offset = (int) ($request->query->get('offset') ?? 0);

      // Validate parameters.
      $limit = max(1, min(100, $limit));
      $offset = max(0, $offset);

      // Get workflows from entity storage.
      $storage = $this->entityTypeManager()->getStorage('flowdrop_workflow');

      // Compute total count via a separate count query before pagination.
      $countQuery = $storage->getQuery();
      if ($search) {
        $countQuery = $countQuery->condition('label', '%' . $search . '%', 'LIKE');
      }
      $total = (int) $countQuery
        ->accessCheck(TRUE)
        ->count()
        ->execute();

      // Fetch paginated results.
      $query = $storage->getQuery();
      if ($search) {
        $query = $query->condition('label', '%' . $search . '%', 'LIKE');
      }

      $ids = $query
        ->sort('changed', 'DESC')
        ->accessCheck(TRUE)
        ->range($offset, $limit)
        ->execute();

      $workflows = $storage->loadMultiple($ids);

      $data = [];
      foreach ($workflows as $workflow) {
        if ($workflow instanceof WorkflowDefinitionInterface) {
          $data[] = [
            'id' => $workflow->id(),
            'name' => $workflow->label(),
            'description' => $workflow->getDescription(),
            'nodes' => $this->metadataResolver->enrichNodes($workflow->getNodes()),
            'edges' => $workflow->getEdges(),
            'metadata' => $workflow->getMetadata(),
            'created' => $workflow->getCreated(),
            'changed' => $workflow->getChanged(),
            'uid' => $workflow->getUid(),
          ];
        }
      }

      return $this->paginatedResponse($data, $total, $limit, $offset);

    }
    catch (HttpExceptionInterface $e) {
      throw $e;
    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop')->error('Error fetching workflows: @error', ['@error' => $e->getMessage()]);

      return $this->errorResponse('Failed to fetch workflows', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Create a new workflow.
   */
  public function createWorkflow(Request $request): JsonResponse {
    try {
      $data = $this->decodeJsonRequest($request);

      // Validate required fields.
      if (empty($data['name'])) {
        throw new BadRequestHttpException('Workflow name is required');
      }

      if (strlen($data['name']) > 255) {
        throw new BadRequestHttpException('Workflow name too long (max 255 characters)');
      }

      // Reject if client-supplied ID exists (prevents silent overwrite).
      if (!empty($data['id'])) {
        $existing = $this->entityTypeManager()->getStorage('flowdrop_workflow')->load($data['id']);
        if ($existing) {
          return $this->errorResponse('A workflow with this ID already exists', Response::HTTP_CONFLICT);
        }
      }

      // Create workflow entity.
      $workflow = $this->entityTypeManager()->getStorage('flowdrop_workflow')->create([
        'id' => $data['id'] ?? $this->generateWorkflowId(),
        'label' => trim($data['name']),
        'description' => trim($data['description'] ?? ''),
        'nodes' => $data['nodes'] ?? [],
        'edges' => $data['edges'] ?? [],
        'metadata' => $data['metadata'] ?? [],
        'uid' => $this->currentUser()->id(),
      ]);

      if (!$workflow instanceof WorkflowDefinitionInterface) {
        throw new EntityMalformedException("Workflow creation failed");
      }

      if ($invalid = $this->validateStructure($workflow)) {
        return $invalid;
      }

      $workflow->save();

      return $this->successResponse([
        'id' => $workflow->id(),
        'name' => $workflow->label(),
        'description' => $workflow->getDescription(),
        'nodes' => $this->metadataResolver->enrichNodes($workflow->getNodes()),
        'edges' => $workflow->getEdges(),
        'metadata' => $workflow->getMetadata(),
        'created' => $workflow->getCreated(),
        'changed' => $workflow->getChanged(),
        'uid' => $workflow->getUid(),
      ], Response::HTTP_CREATED);

    }
    catch (HttpExceptionInterface $e) {
      throw $e;
    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop')->error('Error creating workflow: @error', ['@error' => $e->getMessage()]);

      return $this->errorResponse('Failed to create workflow', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Get a specific workflow by ID.
   */
  public function getWorkflow(Request $request, string $id): JsonResponse {
    try {
      // Get workflow from entity storage.
      $workflow = $this->entityTypeManager()
        ->getStorage('flowdrop_workflow')
        ->load($id);

      if (!$workflow instanceof WorkflowDefinitionInterface) {
        throw new NotFoundHttpException('Workflow not found');
      }

      return $this->successResponse([
        'id' => $workflow->id(),
        'name' => $workflow->label(),
        'description' => $workflow->getDescription(),
        'nodes' => $this->metadataResolver->enrichNodes($workflow->getNodes()),
        'edges' => $workflow->getEdges(),
        'metadata' => $workflow->getMetadata(),
        'created' => $workflow->getCreated(),
        'changed' => $workflow->getChanged(),
        'uid' => $workflow->getUid(),
      ]);

    }
    catch (HttpExceptionInterface $e) {
      throw $e;
    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop')->error('Error fetching workflow: @error', ['@error' => $e->getMessage()]);

      return $this->errorResponse('Failed to fetch workflow', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Update an existing workflow.
   */
  public function updateWorkflow(Request $request, string $id): JsonResponse {
    try {
      $data = $this->decodeJsonRequest($request);

      // Get existing workflow.
      $workflow = $this->entityTypeManager()
        ->getStorage('flowdrop_workflow')
        ->load($id);

      if (!$workflow instanceof WorkflowDefinitionInterface) {
        throw new NotFoundHttpException('Workflow not found');
      }
      // Validate required fields.
      if (empty($data['name'])) {
        throw new BadRequestHttpException('Workflow name is required');
      }

      if (strlen($data['name']) > 255) {
        throw new BadRequestHttpException('Workflow name too long (max 255 characters)');
      }

      // Update workflow fields.
      if (isset($data['name'])) {
        $workflow->setLabel($data['name']);
      }
      if (isset($data['description'])) {
        $workflow->setDescription($data['description']);
      }

      if (isset($data['nodes'])) {
        $workflow->setNodes($data['nodes']);
      }
      if (isset($data['edges'])) {
        $workflow->setEdges($data['edges']);
      }
      if (isset($data['metadata'])) {
        $workflow->setMetadata($data['metadata']);
      }

      if ($invalid = $this->validateStructure($workflow)) {
        return $invalid;
      }

      $workflow->save();

      return $this->successResponse([
        'id' => $workflow->id(),
        'name' => $workflow->label(),
        'description' => $workflow->getDescription(),
        'nodes' => $this->metadataResolver->enrichNodes($workflow->getNodes()),
        'edges' => $workflow->getEdges(),
        'metadata' => $workflow->getMetadata(),
        'created' => $workflow->getCreated(),
        'changed' => $workflow->getChanged(),
        'uid' => $workflow->getUid(),
      ]);

    }
    catch (HttpExceptionInterface $e) {
      throw $e;
    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop')->error('Error updating workflow: @error', ['@error' => $e->getMessage()]);

      return $this->errorResponse('Failed to update workflow', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Delete a workflow.
   */
  public function deleteWorkflow(Request $request, string $id): JsonResponse {
    try {
      // Get existing workflow.
      $workflow = $this->entityTypeManager()
        ->getStorage('flowdrop_workflow')
        ->load($id);

      if (!$workflow) {
        throw new NotFoundHttpException('Workflow not found');
      }

      // Delete the workflow.
      $workflow->delete();

      return $this->successResponse(NULL);

    }
    catch (HttpExceptionInterface $e) {
      throw $e;
    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop')->error('Error deleting workflow: @error', ['@error' => $e->getMessage()]);

      return $this->errorResponse('Failed to delete workflow', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Runs the shared structural validator over a prospective workflow.
   *
   * @param \Drupal\flowdrop_workflow\WorkflowDefinitionInterface $workflow
   *   The unsaved workflow to validate.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse|null
   *   A 422 response describing the failures, or NULL when the workflow is
   *   structurally valid.
   */
  protected function validateStructure(WorkflowDefinitionInterface $workflow): ?JsonResponse {
    $result = $this->validator->validate(
      $workflow->getNodes(),
      $workflow->getEdges(),
      $workflow->getInputPorts(),
      $workflow->getOutputPorts(),
    );
    if ($result->isValid()) {
      return NULL;
    }

    return new JsonResponse([
      'success' => FALSE,
      'error' => 'Workflow validation failed',
      'details' => array_map(
        static fn (array $error): array => [
          'code' => $error['code'],
          'message' => $error['message'],
          'locator' => $error['parameter'],
        ],
        $result->toArray()['errors'],
      ),
    ], Response::HTTP_UNPROCESSABLE_ENTITY);
  }

  /**
   * Generate a unique workflow ID.
   *
   * @return string
   *   A unique workflow ID.
   */
  protected function generateWorkflowId(): string {
    return 'workflow_' . bin2hex(random_bytes(8));
  }

  /**
   * Import a workflow from a signed bundle.
   *
   * Accepts a bundle envelope in the request body. Untrusted/unsigned bundles
   * are rejected unless the caller holds the untrusted-import permission AND
   * sends "confirm_untrusted": true. Pass "dry_run": true (body or query) to
   * preview the capability manifest without writing anything.
   */
  public function importWorkflow(Request $request): JsonResponse {
    try {
      $bundle = $this->decodeJsonRequest($request);

      $dryRun = filter_var(
        $request->query->get('dry_run', $bundle['dry_run'] ?? FALSE),
        FILTER_VALIDATE_BOOL,
      );
      $allowUntrusted = !empty($bundle['confirm_untrusted'])
        && $this->currentUser()->hasPermission('import untrusted flowdrop_workflow');

      $result = $this->bundleImporter->import($bundle, $dryRun, $allowUntrusted);

      return $this->importResponse($result);
    }
    catch (HttpExceptionInterface $e) {
      throw $e;
    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop')->error('Error importing workflow bundle: @error', ['@error' => $e->getMessage()]);

      return $this->errorResponse('Failed to import workflow bundle', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Export a workflow as an unsigned, portable bundle.
   *
   * The result is an unsigned bundle envelope; signing is a publisher
   * operation performed off-site with a private key, never here.
   */
  public function exportWorkflow(Request $request, string $id): JsonResponse {
    try {
      $workflow = $this->entityTypeManager()->getStorage('flowdrop_workflow')->load($id);
      if (!$workflow instanceof WorkflowDefinitionInterface) {
        throw new NotFoundHttpException('Workflow not found');
      }

      return $this->successResponse($this->bundleExporter->export($workflow));
    }
    catch (HttpExceptionInterface $e) {
      throw $e;
    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop')->error('Error exporting workflow: @error', ['@error' => $e->getMessage()]);

      return $this->errorResponse('Failed to export workflow', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Maps a bundle import result to an HTTP response.
   *
   * @param \Drupal\flowdrop_workflow\DTO\BundleImportResult $result
   *   The import outcome.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   The response, with a status code reflecting the outcome.
   */
  private function importResponse(BundleImportResult $result): JsonResponse {
    $verification = $result->getVerification();
    $signature = [
      'status' => $verification->getStatus(),
      'key_id' => $verification->getKeyId(),
      'reason' => $verification->getReason(),
    ];
    $manifest = $result->getManifest()?->toArray();

    return match ($result->getStatus()) {
      BundleImportResult::UNSUPPORTED_FORMAT => $this->errorResponse(
        'Unsupported bundle format',
        Response::HTTP_UNPROCESSABLE_ENTITY,
      ),

      BundleImportResult::UNTRUSTED_BLOCKED => new JsonResponse([
        'success' => FALSE,
        'error' => 'Bundle publisher is not trusted',
        'signature' => $signature,
        'hint' => 'Add the publisher under Trusted Publishers, or resend with "confirm_untrusted": true if you hold the untrusted-import permission.',
      ], Response::HTTP_FORBIDDEN),

      BundleImportResult::INVALID => new JsonResponse([
        'success' => FALSE,
        'error' => 'Workflow validation failed',
        'signature' => $signature,
        'details' => array_map(
          static fn (array $error): array => [
            'code' => $error['code'],
            'message' => $error['message'],
            'locator' => $error['parameter'],
          ],
          $result->getValidation()?->toArray()['errors'] ?? [],
        ),
      ], Response::HTTP_UNPROCESSABLE_ENTITY),

      BundleImportResult::MISSING_CAPABILITY => new JsonResponse([
        'success' => FALSE,
        'error' => 'Required processors are not installed',
        'signature' => $signature,
        'manifest' => $manifest,
      ], Response::HTTP_UNPROCESSABLE_ENTITY),

      BundleImportResult::CONFLICT => $this->errorResponse('A workflow with this ID already exists', Response::HTTP_CONFLICT),

      BundleImportResult::PREFLIGHT_OK => $this->successResponse([
        'dry_run' => TRUE,
        'can_apply' => TRUE,
        'signature' => $signature,
        'manifest' => $manifest,
      ]),

      BundleImportResult::APPLIED => $this->successResponse([
        'id' => $result->getWorkflowId(),
        'signature' => $signature,
        'generated_node_types' => $result->getGeneratedNodeTypes(),
        'manifest' => $manifest,
      ], Response::HTTP_CREATED),

      default => $this->errorResponse('Unknown import outcome', Response::HTTP_INTERNAL_SERVER_ERROR),
    };
  }

}
