<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media_streaming\Plugin\FFmpegOutput;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\ffmpeg_media\Attribute\FFmpegOutput;
use Drupal\ffmpeg_media\ValueObject\ConversionOptions;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * WHIP (WebRTC-HTTP Ingestion Protocol) output format plugin.
 *
 * Provides sub-second latency live streaming using WebRTC-based delivery.
 * Requires FFmpeg 8.0 or later for WHIP muxer support.
 *
 * @see https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html
 */
#[FFmpegOutput(
  id: 'whip',
  label: new TranslatableMarkup('WHIP (WebRTC-HTTP Ingestion Protocol)'),
  description: new TranslatableMarkup('Sub-second latency live streaming using WebRTC-based delivery.'),
  extension: '',
  mimeType: 'application/x-whip',
  supportedCodecs: ['h264', 'vp9', 'av1'],
  defaultCodec: 'h264',
  supportedAudioCodecs: ['libopus'],
  weight: 40,
)]
final class WhipOutput extends LowLatencyStreamingOutputBase {

  /**
   * Constructs a WhipOutput plugin.
   *
   * @param array<string, mixed> $configuration
   *   Plugin configuration.
   * @param string $plugin_id
   *   Plugin ID.
   * @param mixed $plugin_definition
   *   Plugin definition.
   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
   *   File system service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Config factory.
   * @param \Drupal\Core\State\StateInterface $state
   *   State service for credential storage.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    #[Autowire(service: 'file_system')]
    FileSystemInterface $fileSystem,
    ConfigFactoryInterface $configFactory,
    #[Autowire(service: 'state')]
    protected readonly StateInterface $state,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $fileSystem, $configFactory);
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function buildArguments(ConversionOptions $options): array {
    $config = $this->configFactory->get('ffmpeg_media_streaming.settings');

    $args = [
      '-f', 'whip',
    ];

    // Add low-latency encoding options for real-time streaming.
    $args = array_merge($args, $this->buildLowLatencyOptions());

    // Apply configured bitrates for real-time streaming constraints.
    $videoBitrate = $config->get('whip_video_bitrate');
    if ($videoBitrate) {
      $args[] = '-b:v';
      $args[] = (string) $videoBitrate;
    }

    $audioBitrate = $config->get('whip_audio_bitrate');
    if ($audioBitrate) {
      $args[] = '-b:a';
      $args[] = (string) $audioBitrate;
    }

    // Use FFmpeg 8.0+ native -authorization parameter (avoids token
    // exposure in process command line via -headers).
    // Resolution order: ENV -> State API -> config (deprecated).
    $bearerToken = $this->resolveWhipBearerToken();
    if ($bearerToken !== '') {
      $args[] = '-authorization';
      $args[] = 'Bearer ' . $bearerToken;
    }

    return $args;
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  #[\NoDiscard]
  public function generateOutputPath(string $inputPath): string {
    $config = $this->configFactory->get('ffmpeg_media_streaming.settings');
    $endpointUrl = $config->get('whip_endpoint_url') ?? '';

    // WHIP streams to a URL endpoint, not a file path.
    if (!empty($endpointUrl)) {
      return $endpointUrl;
    }

    // Fallback if no endpoint is configured.
    return 'whip://localhost:8080/stream';
  }

  /**
   * Builds low-latency encoding options for real-time WebRTC streaming.
   *
   * @return array<int, string>
   *   FFmpeg arguments for low-latency encoding.
   */
  #[\NoDiscard]
  protected function buildLowLatencyOptions(): array {
    return [
      ...$this->buildBaseLowLatencyOptions(),
      // Use zerolatency tune if available (H.264/HEVC).
      '-tune', 'zerolatency',
    ];
  }

  /**
   * Builds arguments for specific codec optimizations in WebRTC context.
   *
   * @param string $codecId
   *   The codec plugin ID.
   *
   * @return array<int, string>
   *   FFmpeg arguments optimized for the specified codec.
   */
  #[\Override]
  #[\NoDiscard]
  public function buildCodecSpecificArguments(string $codecId): array {
    return match ($codecId) {
      'h264' => [
        '-profile:v', 'baseline',
        '-level', '3.1',
      ],
      'vp9' => [
        '-deadline', 'realtime',
        '-cpu-used', '8',
        '-row-mt', '1',
      ],
      'av1' => [
        '-cpu-used', '10',
        '-row-mt', '1',
        '-tiles', '2x2',
        '-usage', 'realtime',
      ],
      default => [],
    };
  }

  /**
   * Validates the WHIP endpoint URL.
   *
   * @param string $url
   *   The WHIP endpoint URL to validate.
   *
   * @return bool
   *   TRUE if the URL is valid for WHIP streaming.
   */
  #[\NoDiscard]
  public function validateEndpointUrl(string $url): bool {
    if (empty($url)) {
      return FALSE;
    }

    $parsed = parse_url($url);
    if ($parsed === FALSE) {
      return FALSE;
    }

    // WHIP endpoints must use HTTP or HTTPS.
    $scheme = $parsed['scheme'] ?? '';
    return in_array($scheme, ['http', 'https', 'whip', 'whips'], TRUE);
  }

  /**
   * Gets the default WHIP configuration.
   *
   * @return array<string, int|string>
   *   Default configuration values for WHIP streaming.
   */
  #[\NoDiscard]
  public function getDefaultConfiguration(): array {
    return [
      'endpoint_url' => '',
      'bearer_token' => '',
      'ice_servers' => 'stun:stun.l.google.com:19302',
      'video_bitrate' => '2500k',
      'audio_bitrate' => '128k',
      'keyframe_interval' => 30,
    ];
  }

  /**
   * Resolves the WHIP bearer token from the credential hierarchy.
   *
   * Resolution order: ENV variable -> State API -> config (deprecated).
   *
   * @return string
   *   The resolved bearer token, or empty string if none configured.
   */
  private function resolveWhipBearerToken(): string {
    // 1. Environment variable (highest priority).
    $envToken = getenv('FFMPEG_WHIP_BEARER_TOKEN');
    if (is_string($envToken) && $envToken !== '') {
      return $envToken;
    }

    // 2. State API (non-exportable).
    $stateToken = $this->state->get('ffmpeg_media_streaming.whip_bearer_token');
    if (is_string($stateToken) && $stateToken !== '') {
      return $stateToken;
    }

    // 3. Legacy config fallback (deprecated since alpha7).
    $configToken = $this->configFactory
      ->get('ffmpeg_media_streaming.settings')
      ->get('whip_bearer_token');
    if (is_string($configToken) && $configToken !== '') {
      @trigger_error('Reading whip_bearer_token from exportable configuration is deprecated in ffmpeg_media_streaming:1.0.0-alpha7 and will be removed in ffmpeg_media_streaming:2.0.0. Use the FFMPEG_WHIP_BEARER_TOKEN environment variable or the admin settings form (State API) instead.', \E_USER_DEPRECATED);
      return $configToken;
    }

    return '';
  }

}
