<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Service;

use Drupal\Core\Database\Connection;
use Drupal\ffmpeg_media\Utility\QueueItemKey;

/**
 * Persists queue-editor metadata on tracked queue rows.
 *
 * @internal
 */
final class QueueMetadataRepository implements QueueMetadataRepositoryInterface {

  /**
   * Queue tracking table.
   */
  private const string TABLE = 'ffmpeg_media_queued';

  /**
   * Constructs a QueueMetadataRepository object.
   */
  public function __construct(
    private readonly Connection $database,
  ) {}

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function loadSchedulingMaps(array $itemIds = []): array {
    $query = $this->database->select(self::TABLE, 'q')
      ->fields('q', ['media_id', 'field_name', 'manual_order', 'priority', 'paused']);

    $allowedKeys = [];
    if ($itemIds !== []) {
      $mediaIds = [];
      foreach ($itemIds as $itemId) {
        if (!QueueItemKey::isValid($itemId)) {
          continue;
        }
        $parts = QueueItemKey::parse($itemId);
        $normalized = QueueItemKey::fromParts($parts['media_id'], $parts['field_name']);
        $allowedKeys[$normalized] = TRUE;
        $mediaIds[$parts['media_id']] = $parts['media_id'];
      }

      if ($allowedKeys === []) {
        return [
          'order' => [],
          'priorities' => [],
          'paused' => [],
        ];
      }

      $query->condition('media_id', array_values($mediaIds), 'IN');
    }

    $statement = $query->execute();
    if ($statement === NULL) {
      return [
        'order' => [],
        'priorities' => [],
        'paused' => [],
      ];
    }

    $metadata = [
      'order' => [],
      'priorities' => [],
      'paused' => [],
    ];

    foreach ($statement as $row) {
      $itemId = QueueItemKey::fromParts(
        (int) ($row->media_id ?? 0),
        (string) ($row->field_name ?? ''),
      );

      if ($allowedKeys !== [] && !isset($allowedKeys[$itemId])) {
        continue;
      }

      $metadata['order'][$itemId] = (int) ($row->manual_order ?? self::DEFAULT_MANUAL_ORDER);
      $metadata['priorities'][$itemId] = max(0, min(2, (int) ($row->priority ?? 0)));
      $metadata['paused'][$itemId] = ((int) ($row->paused ?? 0)) === 1;
    }

    return $metadata;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function replaceManualOrder(array $itemIds): int {
    $normalizedIds = $this->normalizeItemIds($itemIds);

    $transaction = $this->database->startTransaction();
    try {
      $this->database->update(self::TABLE)
        ->fields(['manual_order' => self::DEFAULT_MANUAL_ORDER])
        ->execute();

      foreach ($normalizedIds as $position => $itemId) {
        $parts = QueueItemKey::parse($itemId);
        $this->database->update(self::TABLE)
          ->fields(['manual_order' => $position])
          ->condition('media_id', $parts['media_id'])
          ->condition('field_name', $parts['field_name'])
          ->execute();
      }
    }
    catch (\Throwable $e) {
      $transaction->rollBack();
      throw $e;
    }

    return count($normalizedIds);
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function setPaused(string $itemId, bool $paused): bool {
    return $this->updateItemFields($itemId, ['paused' => $paused ? 1 : 0]) > 0;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function setPriority(string $itemId, int $priority): bool {
    $priority = max(0, min(2, $priority));
    return $this->updateItemFields($itemId, ['priority' => $priority]) > 0;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function clearItemMetadata(string $itemId): void {
    $this->updateItemFields($itemId, [
      'manual_order' => self::DEFAULT_MANUAL_ORDER,
      'priority' => 0,
      'paused' => 0,
    ]);
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function loadOrderedRows(int $offset, int $limit): array {
    if ($limit <= 0) {
      return [];
    }

    $statement = $this->database->select(self::TABLE, 'q')
      ->fields('q', [
        'media_id',
        'field_name',
        'queued_at',
        'profile_id',
        'manual_order',
        'priority',
        'paused',
      ])
      ->orderBy('manual_order', 'ASC')
      ->orderBy('priority', 'DESC')
      ->orderBy('queued_at', 'ASC')
      ->orderBy('media_id', 'ASC')
      ->orderBy('field_name', 'ASC')
      ->range($offset, $limit)
      ->execute();

    return $statement?->fetchAll() ?? [];
  }

  /**
   * Updates queue-row fields for a queue item.
   *
   * @param string $itemId
   *   Queue item ID.
   * @param array<string, int> $fields
   *   Fields to update.
   *
   * @return int
   *   Number of affected rows.
   */
  private function updateItemFields(string $itemId, array $fields): int {
    if (!QueueItemKey::isValid($itemId)) {
      return 0;
    }

    $parts = QueueItemKey::parse($itemId);
    return (int) $this->database->update(self::TABLE)
      ->fields($fields)
      ->condition('media_id', $parts['media_id'])
      ->condition('field_name', $parts['field_name'])
      ->execute();
  }

  /**
   * Normalizes queue item IDs.
   *
   * @param array<int, string> $itemIds
   *   Raw queue item IDs.
   *
   * @return array<int, string>
   *   Valid, unique item IDs.
   */
  private function normalizeItemIds(array $itemIds): array {
    $normalized = [];
    foreach ($itemIds as $itemId) {
      if (!QueueItemKey::isValid($itemId)) {
        continue;
      }
      $parts = QueueItemKey::parse($itemId);
      $normalized[] = QueueItemKey::fromParts($parts['media_id'], $parts['field_name']);
    }

    return array_values(array_unique($normalized));
  }

}
