<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Service;

use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\ffmpeg_media\Enum\ConversionStatus;
use Drupal\ffmpeg_media\Enum\HardwareType;
use Drupal\ffmpeg_media\Event\ConversionCompletedEvent;
use Drupal\ffmpeg_media\Event\ConversionFailedEvent;
use Drupal\ffmpeg_media\Event\ConversionStartedEvent;
use Drupal\ffmpeg_media\Event\FFmpegMediaEvents;
use Drupal\ffmpeg_media\ValueObject\ConversionOptions;
use Drupal\ffmpeg_media\ValueObject\ConversionResult;
use Drupal\ffmpeg_media\ValueObject\ConversionContext;
use Drupal\file\FileInterface;
use Drupal\media\MediaInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
 * Handles conversion lifecycle events (start, success, failure).
 *
 * @internal
 */
final class ConversionLifecycleHandler implements ConversionLifecycleHandlerInterface {

  /**
   * Constructs a ConversionLifecycleHandler object.
   *
   * @param \Drupal\ffmpeg_media\Service\ProgressTrackerInterface $progressTracker
   *   Progress tracker service.
   * @param \Drupal\ffmpeg_media\Service\QueueManagerInterface $queueManager
   *   Queue manager service.
   * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $eventDispatcher
   *   Event dispatcher.
   * @param \Drupal\Core\Logger\LoggerChannelInterface $logger
   *   Logger channel.
   * @param \Drupal\ffmpeg_media\Service\HardwareDetectorInterface $hardwareDetector
   *   Hardware detector service.
   * @param \Drupal\ffmpeg_media\Service\OriginalFileRetentionServiceInterface $originalFileRetention
   *   Original file retention service.
   * @param \Drupal\ffmpeg_media\Service\SegmentCleanupServiceInterface $segmentCleanup
   *   Segment cleanup service for streaming outputs.
   */
  public function __construct(
    protected readonly ProgressTrackerInterface $progressTracker,
    protected readonly QueueManagerInterface $queueManager,
    protected readonly EventDispatcherInterface $eventDispatcher,
    protected readonly LoggerChannelInterface $logger,
    protected readonly HardwareDetectorInterface $hardwareDetector,
    protected readonly OriginalFileRetentionServiceInterface $originalFileRetention,
    protected readonly SegmentCleanupServiceInterface $segmentCleanup,
  ) {}

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function dispatchStarted(
    MediaInterface $media,
    ConversionOptions $conversionOptions,
    string $sourcePath,
    array $outputFormats,
  ): void {
    $this->eventDispatcher->dispatch(
      new ConversionStartedEvent($media, $conversionOptions, $sourcePath, $outputFormats),
      FFmpegMediaEvents::CONVERSION_STARTED,
    );

    $this->progressTracker->updateProgress(
      (int) $media->id(),
      ConversionStatus::Converting,
      0,
      'Starting conversion',
      NULL,
      NULL,
      $conversionOptions->fieldName,
    );
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function dispatchCompleted(
    MediaInterface $media,
    ConversionResult $result,
  ): void {
    $this->eventDispatcher->dispatch(
      new ConversionCompletedEvent($media, $result),
      FFmpegMediaEvents::CONVERSION_COMPLETED,
    );
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function handleSuccess(
    MediaInterface $media,
    ConversionResult $result,
    ConversionContext $context,
    ?FileInterface $sourceFile,
    ?HardwareType $hwType = NULL,
  ): void {
    // Record hardware encoding success for circuit breaker.
    if ($hwType !== NULL) {
      $this->hardwareDetector->recordEncodingSuccess($hwType->value);
    }

    // Handle original file based on retention settings.
    if ($sourceFile !== NULL) {
      $this->originalFileRetention->schedule($media, $context, $sourceFile);
    }
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function finalizeSuccess(
    MediaInterface $media,
    ConversionResult $result,
    ConversionContext $context,
    ?FileInterface $sourceFile,
    ?HardwareType $hwType = NULL,
  ): void {
    $this->dispatchCompleted($media, $result);
    $this->handleSuccess($media, $result, $context, $sourceFile, $hwType);
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function handleFailure(
    MediaInterface $media,
    string $error,
    ?\Throwable $exception = NULL,
    ?HardwareType $hwType = NULL,
    string $fieldName = '',
  ): void {
    $mediaId = (int) $media->id();

    // Record hardware encoding failure for circuit breaker.
    if ($hwType !== NULL) {
      try {
        $this->hardwareDetector->recordEncodingFailure($hwType->value);
      }
      catch (\Throwable $e) {
        $this->logger->error('handleFailure operation @op failed for media @id: @message', [
          '@op' => 'recordEncodingFailure',
          '@id' => $mediaId,
          '@message' => $e->getMessage(),
        ]);
      }
    }

    try {
      $this->progressTracker->markFailed($mediaId, $error, $fieldName);
    }
    catch (\Throwable $e) {
      $this->logger->error('handleFailure operation @op failed for media @id: @message', [
        '@op' => 'markFailed',
        '@id' => $mediaId,
        '@message' => $e->getMessage(),
      ]);
    }

    try {
      $this->queueManager->removeFromQueue($mediaId, $fieldName);
    }
    catch (\Throwable $e) {
      $this->logger->error('handleFailure operation @op failed for media @id: @message', [
        '@op' => 'removeFromQueue',
        '@id' => $mediaId,
        '@message' => $e->getMessage(),
      ]);
    }

    // Clean up any partial streaming segments left by the failed conversion.
    try {
      $this->segmentCleanup->cleanupSegmentsForMedia($mediaId, $fieldName);
    }
    catch (\Throwable $e) {
      $this->logger->error('handleFailure operation @op failed for media @id: @message', [
        '@op' => 'cleanupSegments',
        '@id' => $mediaId,
        '@message' => $e->getMessage(),
      ]);
    }

    try {
      $this->logger->error('Conversion failed for media @id: @error', [
        '@id' => $mediaId,
        '@error' => $error,
      ]);
    }
    catch (\Throwable) {
      // Logger failed — event dispatch must still proceed.
    }

    try {
      $this->eventDispatcher->dispatch(
        new ConversionFailedEvent($media, $error, $exception),
        FFmpegMediaEvents::CONVERSION_FAILED,
      );
    }
    catch (\Throwable $e) {
      $this->logger->error('handleFailure operation @op failed for media @id: @message', [
        '@op' => 'dispatchFailedEvent',
        '@id' => $mediaId,
        '@message' => $e->getMessage(),
      ]);
    }
  }

}
