<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Commands;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\ffmpeg_media\Enum\OutputType;
use Drupal\ffmpeg_media\Service\FFmpegExecutorInterface;
use Drupal\ffmpeg_media\Service\FormatterSettingsResolverInterface;
use Drupal\ffmpeg_media\Service\OutputStorageServiceInterface;
use Drupal\ffmpeg_media\Service\SourceFileResolverInterface;
use Drupal\media\MediaInterface;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Symfony\Component\Process\Process;

/**
 * Drush commands for FFmpeg Media thumbnail generation.
 */
final class ThumbnailCommands extends DrushCommands {

  use CommandHelperTrait;

  /**
   * Constructs a ThumbnailCommands instance.
   *
   * @param \Drupal\ffmpeg_media\Service\FormatterSettingsResolverInterface $formatterSettingsResolver
   *   Formatter settings resolver service.
   * @param \Drupal\ffmpeg_media\Service\OutputStorageServiceInterface $outputStorage
   *   Output storage service.
   * @param \Drupal\ffmpeg_media\Service\SourceFileResolverInterface $sourceFileResolver
   *   Source file 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.
   */
  public function __construct(
    protected readonly FormatterSettingsResolverInterface $formatterSettingsResolver,
    protected readonly OutputStorageServiceInterface $outputStorage,
    protected readonly SourceFileResolverInterface $sourceFileResolver,
    protected readonly FFmpegExecutorInterface $ffmpegExecutor,
    protected readonly EntityTypeManagerInterface $entityTypeManager,
    protected readonly FileSystemInterface $fileSystem,
  ) {
    parent::__construct();
  }

  /**
   * Generate thumbnails for converted videos.
   *
   * @param array $options
   *   Command options.
   *
   * @phpstan-param array{media-id: int|null, force: bool, limit: int, position: float} $options
   */
  #[CLI\Command(name: 'ffmpeg:generate-thumbnails', aliases: ['ffgt'])]
  #[CLI\Option(name: 'media-id', description: 'Process specific media ID only.')]
  #[CLI\Option(name: 'force', description: 'Generate even if thumbnail already exists.')]
  #[CLI\Option(name: 'limit', description: 'Maximum number of videos to process (0 = no limit).')]
  #[CLI\Option(name: 'position', description: 'Position in seconds for thumbnail capture (default: 1.0).')]
  #[CLI\Usage(name: 'ffmpeg:generate-thumbnails', description: 'Generate thumbnails for all converted videos without thumbnails.')]
  #[CLI\Usage(name: 'ffmpeg:generate-thumbnails --media-id=123', description: 'Generate thumbnail for specific video.')]
  #[CLI\Usage(name: 'ffmpeg:generate-thumbnails --force', description: 'Generate thumbnails even if they exist.')]
  #[CLI\Usage(name: 'ffmpeg:generate-thumbnails --position=5.0', description: 'Capture thumbnail at 5 seconds.')]
  public function generateThumbnails(
    array $options = [
      'media-id' => NULL,
      'force' => FALSE,
      'limit' => 0,
      'position' => 1.0,
    ],
  ): void {
    $mediaId = $options['media-id'] !== NULL ? (int) $options['media-id'] : NULL;
    $force = (bool) $options['force'];
    $limit = (int) $options['limit'];
    $position = (float) $options['position'];

    // Get media to process.
    if ($mediaId !== NULL) {
      $mediaIds = [$mediaId];
    }
    else {
      $mediaIds = $this->findConvertedMedia($limit, NULL, $this->entityTypeManager, $this->formatterSettingsResolver, $this->outputStorage);
    }

    if (empty($mediaIds)) {
      $this->logger()?->notice('No videos found to process.');
      return;
    }

    $total = count($mediaIds);
    $this->io()->title("Generating thumbnails for {$total} video(s)");

    $generated = 0;
    $skipped = 0;
    $failed = 0;

    $progressBar = $this->io()->createProgressBar($total);
    $progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%');
    $progressBar->setMessage('Initializing...');
    $progressBar->start();

    $mediaEntities = $this->entityTypeManager->getStorage('media')->loadMultiple($mediaIds);

    foreach ($mediaIds as $id) {
      $progressBar->setMessage("Processing media {$id}...");

      $media = $mediaEntities[$id] ?? NULL;
      if (!$media instanceof MediaInterface) {
        $failed++;
        $progressBar->advance();
        continue;
      }

      $contexts = $this->formatterSettingsResolver->getContextsForEntity($media);
      $context = $contexts[0] ?? NULL;
      if ($context === NULL) {
        $failed++;
        $progressBar->advance();
        continue;
      }

      // Check if thumbnail already exists.
      if (!$force) {
        $existingThumbnail = $this->outputStorage->getOutput((int) $media->id(), $context->fieldName, OutputType::Thumbnail->value);
        if ($existingThumbnail !== NULL) {
          $skipped++;
          $progressBar->advance();
          continue;
        }
      }

      // Find video source for thumbnail.
      $videoPath = $this->findBestVideoSource($media, $context);
      if ($videoPath === NULL) {
        $failed++;
        $progressBar->advance();
        continue;
      }

      // Generate thumbnail.
      if ($this->generateThumbnailForVideo($media, $context, $videoPath, $position)) {
        $generated++;
      }
      else {
        $failed++;
      }

      $progressBar->advance();
    }

    $progressBar->setMessage('Done!');
    $progressBar->finish();
    $this->io()->newLine(2);

    $this->logger()?->success('Thumbnail generation complete: {generated} generated, {skipped} skipped, {failed} failed.', [
      'generated' => $generated,
      'skipped' => $skipped,
      'failed' => $failed,
    ]);
  }

  /**
   * Finds the best video source for thumbnail generation.
   *
   * @param \Drupal\media\MediaInterface $media
   *   Media entity.
   * @param \Drupal\ffmpeg_media\ValueObject\ConversionContext $context
   *   Conversion context.
   *
   * @return string|null
   *   Path to video file or NULL.
   */
  protected function findBestVideoSource(MediaInterface $media, $context): ?string {
    // Prefer MP4 (better codec support for thumbnails).
    foreach ([OutputType::Mp4->value, OutputType::WebM->value, 'source'] as $type) {
      if ($type === 'source') {
        $path = $this->sourceFileResolver->getSourcePath($media, $context->fieldName);
      }
      else {
        $file = $this->outputStorage->getOutput((int) $media->id(), $context->fieldName, $type);
        $path = $file?->getFileUri();
      }

      if ($path !== NULL) {
        $localPath = $this->resolveLocalPath($path, $this->fileSystem);
        if ($localPath !== NULL && file_exists($localPath)) {
          return $path;
        }
      }
    }

    return NULL;
  }

  /**
   * Generates a thumbnail for a video.
   *
   * @param \Drupal\media\MediaInterface $media
   *   Media entity.
   * @param \Drupal\ffmpeg_media\ValueObject\ConversionContext $context
   *   Conversion context.
   * @param string $videoPath
   *   Path to video file.
   * @param float $position
   *   Position in seconds.
   *
   * @return bool
   *   TRUE on success.
   */
  protected function generateThumbnailForVideo(MediaInterface $media, $context, string $videoPath, float $position): bool {
    $mediaId = (int) $media->id();

    // Determine output path.
    $pathInfo = pathinfo($videoPath);
    $dirname = $pathInfo['dirname'] ?? dirname($videoPath);
    $thumbnailCandidate = $dirname . '/' . $pathInfo['filename'] . '_thumb.webp';
    $thumbnailUri = $this->resolveStorageUri($thumbnailCandidate, $this->fileSystem);
    $thumbnailDir = dirname($thumbnailUri);
    $this->fileSystem->prepareDirectory($thumbnailDir, FileSystemInterface::CREATE_DIRECTORY);

    $videoLocalPath = $this->resolveLocalPath($videoPath, $this->fileSystem);
    $thumbnailLocalPath = $this->resolveLocalPath($thumbnailUri, $this->fileSystem);
    if ($videoLocalPath === NULL || $thumbnailLocalPath === NULL) {
      $this->logger()?->warning('Thumbnail generation failed for media {id}: unable to resolve local paths.', [
        'id' => $mediaId,
      ]);
      return FALSE;
    }

    $command = [
      $this->ffmpegExecutor->getFfmpegPath(),
      '-y',
      '-ss', (string) $position,
      '-i', $videoLocalPath,
      '-vframes', '1',
      '-vf', 'scale=-1:360',
      '-c:v', 'libwebp',
      '-quality', '80',
      $thumbnailLocalPath,
    ];

    $process = new Process($command);
    $process->setTimeout(120);
    $process->run();

    if (!$process->isSuccessful() || !file_exists($thumbnailLocalPath)) {
      $this->logger()?->warning('Thumbnail generation failed for media {id}: {output}', [
        'id' => $mediaId,
        'output' => $process->getErrorOutput(),
      ]);
      return FALSE;
    }

    // Create file entity.
    try {
      $fileStorage = $this->entityTypeManager->getStorage('file');

      $file = $fileStorage->create([
        'uri' => $thumbnailUri,
        'filename' => basename($thumbnailUri),
        'filemime' => 'image/webp',
        'status' => 1,
        'uid' => $media->getOwnerId(),
      ]);
      $file->save();

      // Attach to media.
      $this->outputStorage->setOutput((int) $media->id(), $context->fieldName, OutputType::Thumbnail->value, $file);
      $media->save();

      return TRUE;
    }
    catch (\Throwable $e) {
      $this->logger()?->error('Failed to create thumbnail file entity for media {id}: {error}', [
        'id' => $mediaId,
        'error' => $e->getMessage(),
      ]);
      return FALSE;
    }
  }

}
