<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Hook;

use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\ffmpeg_media\ValueObject\EntityHookDependencies;
use Drupal\media\MediaInterface;
use Psr\Log\LoggerInterface;

/**
 * Entity-related hook implementations.
 */
final class EntityHooks {

  use StringTranslationTrait;

  /**
   * Constructs an EntityHooks object.
   *
   * @param \Drupal\ffmpeg_media\ValueObject\EntityHookDependencies $deps
   *   Bundled entity hook dependencies.
   * @param \Psr\Log\LoggerInterface $logger
   *   The FFmpeg Media logger channel.
   */
  public function __construct(
    protected readonly EntityHookDependencies $deps,
    protected readonly LoggerInterface $logger,
  ) {}

  /**
   * Implements hook_media_insert().
   */
  #[Hook('media_insert')]
  public function mediaInsert(MediaInterface $media): void {
    $this->queueIfConfigured($media);
  }

  /**
   * Implements hook_media_update().
   */
  #[Hook('media_update')]
  public function mediaUpdate(MediaInterface $media): void {
    $contexts = $this->deps->formatterSettingsResolver->getContextsForEntity($media);

    foreach ($contexts as $context) {
      if (!$context->autoConvert) {
        continue;
      }

      $originalSourceId = NULL;
      if (isset($media->original) && $media->original instanceof MediaInterface) {
        $originalFile = $this->deps->sourceFileResolver->getSourceFile($media->original, $context->fieldName);
        $originalSourceId = $originalFile?->id();
      }

      $currentFile = $this->deps->sourceFileResolver->getSourceFile($media, $context->fieldName);
      $currentSourceId = $currentFile?->id();

      // Only queue if source file changed.
      if ($currentSourceId !== NULL && $currentSourceId !== $originalSourceId) {
        $sourcePath = $this->deps->sourceFileResolver->getSourcePath($media, $context->fieldName);
        if ($sourcePath !== NULL && file_exists($sourcePath)) {
          // Validate file content via magic bytes before queuing.
          if ($currentFile !== NULL && !$this->deps->sourceFileResolver->validateFileContent($currentFile)) {
            $this->deps->messenger->addWarning($this->t('The uploaded file does not appear to be a valid video file. Conversion skipped.'));
            continue;
          }
          $this->queueAndNotify($media, $context->conversionProfile, $context->fieldName);
        }
      }
    }
  }

  /**
   * Implements hook_media_delete().
   */
  #[Hook('media_delete')]
  public function mediaDelete(MediaInterface $media): void {
    try {
      $this->deps->queueManager->removeFromQueue((int) $media->id());
    }
    catch (\Throwable $e) {
      // Log but don't break entity deletion for other hook subscribers.
      $this->logger->warning('Failed to remove media @id from queue during delete: @error', [
        '@id' => $media->id(),
        '@error' => $e->getMessage(),
      ]);
    }

    try {
      $this->deps->outputStorage->deleteAllOutputs((int) $media->id());
    }
    catch (\Throwable $e) {
      // Log but don't break entity deletion for other hook subscribers.
      $this->logger->warning('Failed to delete outputs for media @id during delete: @error', [
        '@id' => $media->id(),
        '@error' => $e->getMessage(),
      ]);
    }

    try {
      $this->deps->progressTracker->deleteProgress((int) $media->id());
    }
    catch (\Throwable $e) {
      $this->logger->warning('Failed to delete progress for media @id during delete: @error', [
        '@id' => $media->id(),
        '@error' => $e->getMessage(),
      ]);
    }

    try {
      $this->deps->lockManager->releaseLock((int) $media->id());
    }
    catch (\Throwable $e) {
      $this->logger->warning('Failed to release lock for media @id during delete: @error', [
        '@id' => $media->id(),
        '@error' => $e->getMessage(),
      ]);
    }

    try {
      $this->deps->retryManager->resetRetries((int) $media->id());
    }
    catch (\Throwable $e) {
      $this->logger->warning('Failed to reset retries for media @id during delete: @error', [
        '@id' => $media->id(),
        '@error' => $e->getMessage(),
      ]);
    }
  }

  /**
   * Queues media for conversion if auto-convert is enabled.
   *
   * @param \Drupal\media\MediaInterface $media
   *   The media entity.
   */
  protected function queueIfConfigured(MediaInterface $media): void {
    $contexts = $this->deps->formatterSettingsResolver->getContextsForEntity($media);

    foreach ($contexts as $context) {
      if (!$context->autoConvert) {
        continue;
      }

      $sourcePath = $this->deps->sourceFileResolver->getSourcePath($media, $context->fieldName);
      if ($sourcePath === NULL || !file_exists($sourcePath)) {
        continue;
      }

      // Validate file content via magic bytes before queuing.
      $sourceFile = $this->deps->sourceFileResolver->getSourceFile($media, $context->fieldName);
      if ($sourceFile !== NULL && !$this->deps->sourceFileResolver->validateFileContent($sourceFile)) {
        $this->deps->messenger->addWarning($this->t('The uploaded file does not appear to be a valid video file. Conversion skipped.'));
        continue;
      }

      $this->queueAndNotify($media, $context->conversionProfile, $context->fieldName);
    }
  }

  /**
   * Queues a media entity and notifies the user.
   *
   * @param \Drupal\media\MediaInterface $media
   *   The media entity.
   * @param string|null $conversionProfile
   *   The conversion profile ID.
   * @param string $fieldName
   *   The source field name.
   */
  protected function queueAndNotify(MediaInterface $media, ?string $conversionProfile, string $fieldName): void {
    $queued = $this->deps->queueManager->queueForConversion(
      $media,
      'convert',
      $conversionProfile,
      fieldName: $fieldName,
    );

    if (!$queued) {
      return;
    }

    if ($this->deps->currentUser->hasPermission('view conversion progress')) {
      if ($this->deps->moduleHandler->moduleExists('ffmpeg_media_ui')) {
        $this->deps->messenger->addStatus($this->t(
          'Video queued for conversion. <a href="@url">Track progress</a>. Conversions run via cron or <code>drush queue:run ffmpeg_media_conversion</code> and <code>drush queue:run ffmpeg_media_conversion_large</code>.',
          ['@url' => Url::fromRoute('ffmpeg_media_ui.dashboard')->toString()],
        ));
      }
      else {
        $this->deps->messenger->addStatus($this->t(
          'Video queued for conversion. Run via cron or <code>drush queue:run ffmpeg_media_conversion</code> and <code>drush queue:run ffmpeg_media_conversion_large</code>.',
        ));
      }
    }
  }

}
