<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Service;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\file\FileInterface;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;

/**
 * Determines which queue a media item should be assigned to.
 *
 * @internal
 */
final class QueueAssignmentService implements QueueAssignmentServiceInterface {

  /**
   * Table name for queue deduplication.
   */
  private const string TABLE = 'ffmpeg_media_queued';

  /**
   * Constructs a QueueAssignmentService object.
   *
   * @param \Drupal\Core\Database\Connection $database
   *   Database connection.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Config factory.
   * @param \Drupal\Core\Logger\LoggerChannelInterface $logger
   *   Logger channel.
   * @param \Drupal\ffmpeg_media\Service\SourceFileResolverInterface $sourceFileResolver
   *   Source file resolver.
   * @param \Drupal\ffmpeg_media\Service\FormatterSettingsResolverInterface|null $formatterSettingsResolver
   *   Optional formatter settings resolver for multi-context queue safeguards.
   */
  public function __construct(
    private readonly Connection $database,
    private readonly ConfigFactoryInterface $configFactory,
    private readonly LoggerChannelInterface $logger,
    private readonly SourceFileResolverInterface $sourceFileResolver,
    private readonly ?FormatterSettingsResolverInterface $formatterSettingsResolver = NULL,
  ) {}

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getQueueNameForSize(int $fileSize): string {
    $threshold = $this->getConfig('large_file_threshold') ?? QueueManagerInterface::DEFAULT_LARGE_FILE_THRESHOLD;

    return $fileSize > $threshold
      ? QueueManagerInterface::QUEUE_LARGE_NAME
      : QueueManagerInterface::QUEUE_NAME;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getQueueForFile(FileInterface $file): string {
    return $this->getQueueNameForSize((int) $file->getSize());
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function getLargeFileThreshold(): int {
    return (int) ($this->getConfig('large_file_threshold') ?? QueueManagerInterface::DEFAULT_LARGE_FILE_THRESHOLD);
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function resolveConversionContext(MediaInterface $media, string $fieldName): ?array {
    $mediaId = (int) $media->id();

    if ($fieldName === '' && $this->formatterSettingsResolver !== NULL) {
      $contexts = $this->formatterSettingsResolver->getContextsForEntity($media);
      if (count($contexts) > 1) {
        $this->logger->warning('Refusing to queue media @id without explicit field selection because @count FFmpeg formatter contexts are configured. FFmpeg Media tracks queue state per field; pass a field name explicitly.', [
          '@id' => $mediaId,
          '@count' => count($contexts),
        ]);
        return NULL;
      }
    }

    // Auto-detect source field from media type if not explicitly provided.
    if ($fieldName === '') {
      $source = $media->getSource();
      $mediaType = $media->get('bundle')->entity ?? NULL;
      if ($mediaType instanceof MediaTypeInterface) {
        $sourceFieldDefinition = $source->getSourceFieldDefinition($mediaType);
        $fieldName = $sourceFieldDefinition?->getName() ?? '';
      }
    }

    // Resolve the source file.
    $sourceFile = $fieldName !== ''
      ? $this->sourceFileResolver->getSourceFile($media, $fieldName)
      : NULL;
    if ($sourceFile === NULL) {
      return NULL;
    }

    return [
      'media_id' => $mediaId,
      'field_name' => $fieldName,
      'source_file' => $sourceFile,
    ];
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function resolveQueueTarget(FileInterface $sourceFile, int $mediaId, string $fieldName): ?array {
    $fileSize = (int) $sourceFile->getSize();

    $maxFileSize = $this->getConfig('max_file_size') ?? 524288000;
    if ($fileSize > $maxFileSize) {
      $this->logger->warning('File size @size exceeds maximum @max for media @id', [
        '@size' => $fileSize,
        '@max' => $maxFileSize,
        '@id' => $mediaId,
      ]);
      return NULL;
    }

    // Prevent duplicate field-scoped queue rows while a legacy media-wide
    // dedup marker is still present from older releases.
    if ($fieldName !== '' && $this->hasLegacyQueuedMarker($mediaId)) {
      return NULL;
    }

    return [
      'queue_name' => $this->getQueueNameForSize($fileSize),
      'file_size' => $fileSize,
    ];
  }

  /**
   * Checks whether a legacy media-wide dedup marker still exists.
   *
   * @param int $mediaId
   *   The media entity ID.
   *
   * @return bool
   *   TRUE if a legacy marker exists.
   */
  private function hasLegacyQueuedMarker(int $mediaId): bool {
    $select = $this->database->select(self::TABLE, 'q');
    if ($select === NULL) {
      return FALSE;
    }

    $statement = $select
      ->condition('media_id', $mediaId)
      ->condition('field_name', '')
      ->countQuery()
      ->execute();

    return (bool) ($statement?->fetchField() ?? FALSE);
  }

  /**
   * Gets a config value.
   *
   * @param string $key
   *   Config key.
   *
   * @return mixed
   *   Config value.
   */
  private function getConfig(string $key): mixed {
    return $this->configFactory->get('ffmpeg_media.settings')->get($key);
  }

}
