<?php

declare(strict_types=1);

namespace Drupal\flowdrop_stategraph\ApprovalGate;

use Drupal\flowdrop_stategraph\DTO\Checkpoint;
use Drupal\flowdrop_stategraph\Event\ApprovalGateApprovedEvent;
use Drupal\flowdrop_stategraph\Event\ApprovalGateRejectedEvent;
use Drupal\flowdrop_stategraph\Event\ApprovalGateRequestedEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Handler for approval gate operations.
 *
 * This service manages the lifecycle of approval requests, including:
 * - Creating new approval requests.
 * - Tracking approval status.
 * - Processing approve/reject actions.
 * - Dispatching events for integrations.
 *
 * Approval requests are stored in memory for this implementation.
 * For production, consider using a dedicated entity or database table.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class ApprovalGateHandler implements ApprovalGateInterface {

  /**
   * In-memory storage for approval requests.
   *
   * @var array<string, array{nodeId: string, checkpoint: Checkpoint, status: ApprovalStatus, context: array<string, mixed>, createdAt: int, resolvedAt: int|null, resolvedBy: int|null, notes: string}>
   */
  private static array $requests = [];

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

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  private readonly EventDispatcherInterface $eventDispatcher;

  /**
   * Constructs a new ApprovalGateHandler.
   *
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger service.
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
   *   The event dispatcher.
   */
  public function __construct(
    LoggerInterface $logger,
    EventDispatcherInterface $eventDispatcher,
  ) {
    $this->logger = $logger;
    $this->eventDispatcher = $eventDispatcher;
  }

  /**
   * {@inheritdoc}
   */
  public function createRequest(
    string $nodeId,
    Checkpoint $checkpoint,
    array $context = [],
  ): string {
    $requestId = "approval_" . bin2hex(random_bytes(12));

    self::$requests[$requestId] = [
      "nodeId" => $nodeId,
      "checkpoint" => $checkpoint,
      "status" => ApprovalStatus::Pending,
      "context" => $context,
      "createdAt" => time(),
      "resolvedAt" => NULL,
      "resolvedBy" => NULL,
      "notes" => "",
    ];

    $this->logger->info("Approval request @id created at node @node", [
      "@id" => $requestId,
      "@node" => $nodeId,
    ]);

    // Dispatch event for integrations (notifications, etc.).
    $this->eventDispatcher->dispatch(
      new ApprovalGateRequestedEvent(
        checkpoint: $checkpoint,
        requestId: $requestId,
        nodeId: $nodeId,
        context: $context,
      ),
      ApprovalGateRequestedEvent::NAME
    );

    return $requestId;
  }

  /**
   * {@inheritdoc}
   */
  public function getStatus(string $requestId): ApprovalStatus {
    if (!isset(self::$requests[$requestId])) {
      $this->logger->warning("Approval request @id not found", [
        "@id" => $requestId,
      ]);
      return ApprovalStatus::Pending;
    }

    return self::$requests[$requestId]["status"];
  }

  /**
   * {@inheritdoc}
   */
  public function approve(
    string $requestId,
    ?int $userId = NULL,
    string $notes = "",
  ): bool {
    if (!isset(self::$requests[$requestId])) {
      $this->logger->warning("Cannot approve: request @id not found", [
        "@id" => $requestId,
      ]);
      return FALSE;
    }

    $request = &self::$requests[$requestId];

    if ($request["status"] !== ApprovalStatus::Pending) {
      $this->logger->warning("Cannot approve: request @id is not pending (status: @status)", [
        "@id" => $requestId,
        "@status" => $request["status"]->value,
      ]);
      return FALSE;
    }

    $request["status"] = ApprovalStatus::Approved;
    $request["resolvedAt"] = time();
    $request["resolvedBy"] = $userId;
    $request["notes"] = $notes;

    $this->logger->info("Approval request @id approved by user @user", [
      "@id" => $requestId,
      "@user" => $userId ?? "system",
    ]);

    // Dispatch approval event.
    $this->eventDispatcher->dispatch(
      new ApprovalGateApprovedEvent(
        checkpoint: $request["checkpoint"],
        requestId: $requestId,
        userId: $userId,
        notes: $notes,
      ),
      ApprovalGateApprovedEvent::NAME
    );

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function reject(
    string $requestId,
    ?int $userId = NULL,
    string $reason = "",
  ): bool {
    if (!isset(self::$requests[$requestId])) {
      $this->logger->warning("Cannot reject: request @id not found", [
        "@id" => $requestId,
      ]);
      return FALSE;
    }

    $request = &self::$requests[$requestId];

    if ($request["status"] !== ApprovalStatus::Pending) {
      $this->logger->warning("Cannot reject: request @id is not pending (status: @status)", [
        "@id" => $requestId,
        "@status" => $request["status"]->value,
      ]);
      return FALSE;
    }

    $request["status"] = ApprovalStatus::Rejected;
    $request["resolvedAt"] = time();
    $request["resolvedBy"] = $userId;
    $request["notes"] = $reason;

    $this->logger->info("Approval request @id rejected by user @user: @reason", [
      "@id" => $requestId,
      "@user" => $userId ?? "system",
      "@reason" => $reason,
    ]);

    // Dispatch rejection event.
    $this->eventDispatcher->dispatch(
      new ApprovalGateRejectedEvent(
        checkpoint: $request["checkpoint"],
        requestId: $requestId,
        userId: $userId,
        reason: $reason,
      ),
      ApprovalGateRejectedEvent::NAME
    );

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function getCheckpoint(string $requestId): ?Checkpoint {
    if (!isset(self::$requests[$requestId])) {
      return NULL;
    }

    return self::$requests[$requestId]["checkpoint"];
  }

  /**
   * {@inheritdoc}
   */
  public function listPending(int $limit = 100): array {
    $pending = [];

    foreach (self::$requests as $id => $request) {
      if ($request["status"] === ApprovalStatus::Pending) {
        $pending[] = [
          "id" => $id,
          "nodeId" => $request["nodeId"],
          "checkpointId" => $request["checkpoint"]->id,
          "createdAt" => $request["createdAt"],
        ];
      }

      if (count($pending) >= $limit) {
        break;
      }
    }

    // Sort by creation time (oldest first).
    usort($pending, function (array $a, array $b): int {
      return $a["createdAt"] <=> $b["createdAt"];
    });

    return $pending;
  }

  /**
   * Gets full details for an approval request.
   *
   * @param string $requestId
   *   The request ID.
   *
   * @return array<string, mixed>|null
   *   The request details or NULL.
   */
  public function getRequest(string $requestId): ?array {
    if (!isset(self::$requests[$requestId])) {
      return NULL;
    }

    $request = self::$requests[$requestId];

    return [
      "id" => $requestId,
      "nodeId" => $request["nodeId"],
      "checkpointId" => $request["checkpoint"]->id,
      "threadId" => $request["checkpoint"]->threadId,
      "status" => $request["status"]->value,
      "context" => $request["context"],
      "createdAt" => $request["createdAt"],
      "resolvedAt" => $request["resolvedAt"],
      "resolvedBy" => $request["resolvedBy"],
      "notes" => $request["notes"],
      "state" => $request["checkpoint"]->state->toArray(),
    ];
  }

  /**
   * Clears all requests (for testing).
   *
   * @return int
   *   Number of requests cleared.
   */
  public function clear(): int {
    $count = count(self::$requests);
    self::$requests = [];
    return $count;
  }

}
