<?php

declare(strict_types=1);

namespace Drupal\flowdrop_runtime\Service\WorkflowState;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\flowdrop_orchestration\DTO\Snapshot\WorkflowSnapshot;
use Drupal\flowdrop_runtime\Entity\FlowDropWorkflowSnapshot;
use Drupal\flowdrop_runtime\Entity\FlowDropWorkflowSnapshotInterface;
use Psr\Log\LoggerInterface;

/**
 * Entity-based persistent storage for workflow snapshots.
 *
 * This service provides persistent storage of WorkflowSnapshot DTOs
 * using the flowdrop_workflow_snapshot entity type. It can be used
 * alongside or instead of the in-memory cache in WorkflowStateManager.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class EntitySnapshotStorage {

  /**
   * The entity storage for workflow snapshots.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  private $storage;

  /**
   * Constructs a new EntitySnapshotStorage.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger service.
   */
  public function __construct(
    private readonly EntityTypeManagerInterface $entityTypeManager,
    private readonly LoggerInterface $logger,
  ) {}

  /**
   * Gets the entity storage, initializing lazily.
   *
   * @return \Drupal\Core\Entity\EntityStorageInterface
   *   The entity storage.
   */
  private function getStorage() {
    if ($this->storage === NULL) {
      $this->storage = $this->entityTypeManager->getStorage("flowdrop_workflow_snapshot");
    }
    return $this->storage;
  }

  /**
   * Saves a WorkflowSnapshot to persistent storage.
   *
   * @param \Drupal\flowdrop_orchestration\DTO\Snapshot\WorkflowSnapshot $snapshot
   *   The snapshot to save.
   *
   * @return \Drupal\flowdrop_runtime\Entity\FlowDropWorkflowSnapshotInterface
   *   The saved entity.
   */
  public function save(WorkflowSnapshot $snapshot): FlowDropWorkflowSnapshotInterface {
    // Check if entity already exists by execution ID.
    $existing = $this->loadByExecutionId($snapshot->executionId);

    if ($existing !== NULL) {
      // Update existing entity.
      $existing->fromWorkflowSnapshot($snapshot);
      $existing->save();

      $this->logger->debug("Updated workflow snapshot entity for execution @exec", [
        "@exec" => $snapshot->executionId,
      ]);

      return $existing;
    }

    // Create new entity.
    $entity = FlowDropWorkflowSnapshot::createFromWorkflowSnapshot($snapshot, $this->getStorage());
    $entity->save();

    $this->logger->debug("Created new workflow snapshot entity @id for execution @exec", [
      "@id" => $entity->id(),
      "@exec" => $snapshot->executionId,
    ]);

    return $entity;
  }

  /**
   * Loads a WorkflowSnapshot by execution ID.
   *
   * @param string $executionId
   *   The execution ID.
   *
   * @return \Drupal\flowdrop_orchestration\DTO\Snapshot\WorkflowSnapshot|null
   *   The snapshot DTO, or NULL if not found.
   */
  public function load(string $executionId): ?WorkflowSnapshot {
    $entity = $this->loadByExecutionId($executionId);
    return $entity?->toWorkflowSnapshot();
  }

  /**
   * Loads the entity by execution ID.
   *
   * @param string $executionId
   *   The execution ID.
   *
   * @return \Drupal\flowdrop_runtime\Entity\FlowDropWorkflowSnapshotInterface|null
   *   The entity, or NULL if not found.
   */
  public function loadByExecutionId(string $executionId): ?FlowDropWorkflowSnapshotInterface {
    $entities = $this->getStorage()->loadByProperties([
      "execution_id" => $executionId,
    ]);

    if (empty($entities)) {
      return NULL;
    }

    $entity = reset($entities);
    return $entity instanceof FlowDropWorkflowSnapshotInterface ? $entity : NULL;
  }

  /**
   * Loads a WorkflowSnapshot by entity ID.
   *
   * @param int|string $entityId
   *   The entity ID.
   *
   * @return \Drupal\flowdrop_orchestration\DTO\Snapshot\WorkflowSnapshot|null
   *   The snapshot DTO, or NULL if not found.
   */
  public function loadByEntityId($entityId): ?WorkflowSnapshot {
    $entity = $this->getStorage()->load($entityId);
    if ($entity instanceof FlowDropWorkflowSnapshotInterface) {
      return $entity->toWorkflowSnapshot();
    }
    return NULL;
  }

  /**
   * Loads the latest snapshot by thread ID.
   *
   * @param string $threadId
   *   The thread ID.
   *
   * @return \Drupal\flowdrop_orchestration\DTO\Snapshot\WorkflowSnapshot|null
   *   The latest snapshot DTO, or NULL if not found.
   */
  public function loadLatestByThread(string $threadId): ?WorkflowSnapshot {
    $query = $this->getStorage()->getQuery()
      ->condition("thread_id", $threadId)
      ->sort("created", "DESC")
      ->range(0, 1)
      // Access bypassed: internal service query; access enforced at API layer.
      ->accessCheck(FALSE);

    $ids = $query->execute();
    if (empty($ids)) {
      return NULL;
    }

    return $this->loadByEntityId(reset($ids));
  }

  /**
   * Deletes a snapshot by execution ID.
   *
   * @param string $executionId
   *   The execution ID.
   *
   * @return bool
   *   TRUE if deleted, FALSE if not found.
   */
  public function delete(string $executionId): bool {
    $entity = $this->loadByExecutionId($executionId);
    if ($entity === NULL) {
      return FALSE;
    }

    $entity->delete();

    $this->logger->debug("Deleted workflow snapshot entity for execution @exec", [
      "@exec" => $executionId,
    ]);

    return TRUE;
  }

  /**
   * Lists snapshots by workflow ID.
   *
   * @param string $workflowId
   *   The workflow ID.
   * @param int $limit
   *   Maximum number of snapshots to return.
   * @param int $offset
   *   Offset for pagination.
   *
   * @return array<\Drupal\flowdrop_orchestration\DTO\Snapshot\WorkflowSnapshot>
   *   Array of snapshot DTOs.
   */
  public function listByWorkflow(string $workflowId, int $limit = 100, int $offset = 0): array {
    $query = $this->getStorage()->getQuery()
      ->condition("workflow_id", $workflowId)
      ->sort("created", "DESC")
      ->range($offset, $limit)
      // Access bypassed: internal service query; access enforced at API layer.
      ->accessCheck(FALSE);

    $ids = $query->execute();
    if (empty($ids)) {
      return [];
    }

    $entities = $this->getStorage()->loadMultiple($ids);
    $snapshots = [];

    foreach ($entities as $entity) {
      if ($entity instanceof FlowDropWorkflowSnapshotInterface) {
        $snapshots[] = $entity->toWorkflowSnapshot();
      }
    }

    return $snapshots;
  }

  /**
   * Lists snapshots by thread ID.
   *
   * @param string $threadId
   *   The thread ID.
   * @param int $limit
   *   Maximum number of snapshots to return.
   *
   * @return array<\Drupal\flowdrop_orchestration\DTO\Snapshot\WorkflowSnapshot>
   *   Array of snapshot DTOs.
   */
  public function listByThread(string $threadId, int $limit = 100): array {
    $query = $this->getStorage()->getQuery()
      ->condition("thread_id", $threadId)
      ->sort("created", "DESC")
      ->range(0, $limit)
      // Access bypassed: internal service query; access enforced at API layer.
      ->accessCheck(FALSE);

    $ids = $query->execute();
    if (empty($ids)) {
      return [];
    }

    $entities = $this->getStorage()->loadMultiple($ids);
    $snapshots = [];

    foreach ($entities as $entity) {
      if ($entity instanceof FlowDropWorkflowSnapshotInterface) {
        $snapshots[] = $entity->toWorkflowSnapshot();
      }
    }

    return $snapshots;
  }

  /**
   * Cleans up old snapshots.
   *
   * @param int $olderThan
   *   Unix timestamp - delete snapshots older than this.
   * @param array<string> $statuses
   *   Optional array of statuses to filter by.
   *
   * @return int
   *   Number of snapshots deleted.
   */
  public function cleanup(int $olderThan, array $statuses = []): int {
    $query = $this->getStorage()->getQuery()
      ->condition("created", $olderThan, "<")
      // Access bypassed: system cron cleanup runs without user context.
      ->accessCheck(FALSE);

    if (!empty($statuses)) {
      $query->condition("snapshot_status", $statuses, "IN");
    }

    $ids = $query->execute();
    if (empty($ids)) {
      return 0;
    }

    $entities = $this->getStorage()->loadMultiple($ids);
    $this->getStorage()->delete($entities);

    $this->logger->info("Cleaned up @count workflow snapshot entities older than @time", [
      "@count" => count($entities),
      "@time" => date("Y-m-d H:i:s", $olderThan),
    ]);

    return count($entities);
  }

  /**
   * Checks if a snapshot exists by execution ID.
   *
   * @param string $executionId
   *   The execution ID.
   *
   * @return bool
   *   TRUE if exists, FALSE otherwise.
   */
  public function exists(string $executionId): bool {
    $query = $this->getStorage()->getQuery()
      ->condition("execution_id", $executionId)
      ->count()
      // Access bypassed: internal service query; access enforced at API layer.
      ->accessCheck(FALSE);

    return $query->execute() > 0;
  }

}
