<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Commands;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Queue\QueueFactory;
use Drupal\ffmpeg_media\Service\FFmpegExecutorInterface;
use Drupal\ffmpeg_media\Service\FieldIntegrityServiceInterface;
use Drupal\ffmpeg_media\Service\FormatterSettingsResolverInterface;
use Drupal\ffmpeg_media\Service\ProgressTrackerInterface;
use Drupal\ffmpeg_media\Service\QueueManagerInterface;
use Drupal\ffmpeg_media\Service\VideoConverterInterface;
use Drupal\media\MediaInterface;
use Drush\Commands\DrushCommands;

/**
 * Shared Drush commands base class for FFmpeg Media module.
 *
 * Individual command groups are in dedicated classes:
 * - ConversionCommands: ffmpeg:convert, ffmpeg:info, ffmpeg:convert-all,
 *   ffmpeg:reconvert, ffmpeg:requeue-missing, ffmpeg:generate-thumbnails
 * - QueueCommands: ffmpeg:process, ffmpeg:queue-clear
 * - SystemCommands: ffmpeg:status, ffmpeg:progress, ffmpeg:processes,
 *   ffmpeg:kill-stuck
 * - CleanupCommands: ffmpeg:cleanup, ffmpeg:integrity-check,
 *   ffmpeg:dead-letters.
 *
 * This class provides shared helper methods used by tests.
 */
final class FFmpegMediaCommands extends DrushCommands {

  use CommandHelperTrait {
    loadMedia as traitLoadMedia;
  }

  /**
   * Constructs an FFmpegMediaCommands instance.
   *
   * @param \Drupal\ffmpeg_media\Service\VideoConverterInterface $videoConverter
   *   Video converter service.
   * @param \Drupal\ffmpeg_media\Service\QueueManagerInterface $queueManager
   *   Queue manager service.
   * @param \Drupal\ffmpeg_media\Service\ProgressTrackerInterface $progressTracker
   *   Progress tracker service.
   * @param \Drupal\ffmpeg_media\Service\FormatterSettingsResolverInterface $formatterSettingsResolver
   *   Formatter settings resolver service.
   * @param \Drupal\ffmpeg_media\Service\FFmpegExecutorInterface $ffmpegExecutor
   *   FFmpeg executor service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   Entity type manager.
   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
   *   File system service.
   * @param \Drupal\Core\Database\Connection $database
   *   Database connection.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Config factory.
   * @param \Drupal\Core\Queue\QueueFactory $queueFactory
   *   Queue factory.
   * @param \Drupal\ffmpeg_media\Service\FieldIntegrityServiceInterface $fieldIntegrity
   *   Field integrity service.
   */
  public function __construct(
    protected readonly VideoConverterInterface $videoConverter,
    protected readonly QueueManagerInterface $queueManager,
    protected readonly ProgressTrackerInterface $progressTracker,
    protected readonly FormatterSettingsResolverInterface $formatterSettingsResolver,
    protected readonly FFmpegExecutorInterface $ffmpegExecutor,
    protected readonly EntityTypeManagerInterface $entityTypeManager,
    protected readonly FileSystemInterface $fileSystem,
    protected readonly Connection $database,
    protected readonly ConfigFactoryInterface $configFactory,
    protected readonly QueueFactory $queueFactory,
    protected readonly FieldIntegrityServiceInterface $fieldIntegrity,
  ) {
    parent::__construct();
  }

  /**
   * Loads and validates a media entity.
   *
   * @param int $mediaId
   *   Media entity ID.
   *
   * @return \Drupal\media\MediaInterface|null
   *   Media entity or NULL if not found/invalid.
   */
  protected function loadMedia(int $mediaId): ?MediaInterface {
    return $this->traitLoadMedia($mediaId, $this->entityTypeManager, $this->formatterSettingsResolver);
  }

  /**
   * Parses runtime string to minutes.
   *
   * @param string $time
   *   Time string like "1:23:45" or "23:45".
   *
   * @return int
   *   Runtime in minutes.
   */
  protected function parseRuntimeMinutes(string $time): int {
    $parts = array_reverse(explode(':', $time));
    $seconds = (int) ($parts[0] ?? 0);
    $minutes = (int) ($parts[1] ?? 0);
    $hours = (int) ($parts[2] ?? 0);

    return $hours * 60 + $minutes + (int) ($seconds / 60);
  }

  /**
   * Resolves a storage URI from a filesystem path where possible.
   *
   * @param string $path
   *   Filesystem path or stream wrapper URI.
   *
   * @return string
   *   Resolved storage URI.
   */
  protected function resolveStorageUri(string $path): string {
    if (str_contains($path, '://')) {
      return $path;
    }

    $candidates = [
      'public://' => $this->fileSystem->realpath('public://'),
      'private://' => $this->fileSystem->realpath('private://'),
    ];

    foreach ($candidates as $scheme => $root) {
      if ($root === FALSE || $root === NULL || $root === '') {
        continue;
      }
      $root = rtrim($root, DIRECTORY_SEPARATOR);
      if ($path === $root) {
        return $scheme;
      }
      $prefix = $root . DIRECTORY_SEPARATOR;
      if (str_starts_with($path, $prefix)) {
        $relative = substr($path, strlen($prefix));
        $relative = str_replace(DIRECTORY_SEPARATOR, '/', $relative);
        return $scheme . $relative;
      }
    }

    return $path;
  }

  /**
   * Resolves a local filesystem path from a stream wrapper URI.
   *
   * @param string $path
   *   Stream wrapper URI or local path.
   *
   * @return string|null
   *   Local path or NULL if unresolvable.
   */
  protected function resolveLocalPath(string $path): ?string {
    if (!str_contains($path, '://')) {
      return $path;
    }

    $directory = dirname($path);
    $realDir = $this->fileSystem->realpath($directory);
    if ($realDir === FALSE) {
      $this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
      $realDir = $this->fileSystem->realpath($directory);
    }

    if ($realDir === FALSE) {
      return NULL;
    }

    return $realDir . DIRECTORY_SEPARATOR . basename($path);
  }

}
