<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Form;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Configure FFmpeg Media settings.
 */
final class SettingsForm extends ConfigFormBase {

  /**
   * Constructs a SettingsForm object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
   *   The typed config manager.
   * @param \Drupal\ffmpeg_media\Form\SettingsFormBuilderInterface $formBuilder
   *   The settings form section builder.
   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager
   *   Stream wrapper manager.
   * @param \Drupal\Core\State\StateInterface $state
   *   State storage for non-exportable secrets.
   */
  public function __construct(
    ConfigFactoryInterface $config_factory,
    TypedConfigManagerInterface $typedConfigManager,
    protected readonly SettingsFormBuilderInterface $formBuilder,
    protected readonly StreamWrapperManagerInterface $streamWrapperManager,
    protected readonly StateInterface $state,
  ) {
    parent::__construct($config_factory, $typedConfigManager);
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public static function create(ContainerInterface $container): static {
    return new static(
      $container->get('config.factory'),
      $container->get('config.typed'),
      $container->get(SettingsFormBuilderInterface::class),
      $container->get('stream_wrapper_manager'),
      $container->get('state'),
    );
  }

  /**
   * {@inheritdoc}
   */
  #[\Override]
  public function getFormId(): string {
    return 'ffmpeg_media_settings';
  }

  /**
   * {@inheritdoc}
   *
   * @return array<int, string>
   *   Editable config names.
   */
  #[\Override]
  protected function getEditableConfigNames(): array {
    return ['ffmpeg_media.settings'];
  }

  /**
   * {@inheritdoc}
   *
   * @param array<string, mixed> $form
   *   Form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state.
   *
   * @return array<string, mixed>
   *   Form elements.
   */
  #[\Override]
  public function buildForm(array $form, FormStateInterface $form_state): array {
    $config = $this->config('ffmpeg_media.settings');

    // Use vertical tabs for organized settings.
    $form['settings_tabs'] = [
      '#type' => 'vertical_tabs',
      '#default_tab' => 'edit-status',
    ];

    $this->formBuilder->buildStatusSection($form, $config);
    $this->formBuilder->buildGeneralSection($form, $config);
    $this->formBuilder->buildFfmpegSection($form, $config);
    $this->formBuilder->buildQueueSection($form, $config);
    $this->formBuilder->buildHardwareSection($form, $config);
    $this->formBuilder->buildProcessorSection($form, $config);
    $this->formBuilder->buildManifestSection($form, $config);
    $this->formBuilder->buildAudioSection($form, $config);
    $this->formBuilder->buildWhisperSection($form, $config);
    $this->formBuilder->buildRetrySection($form, $config);
    $this->formBuilder->buildSecuritySection($form, $config);
    $this->formBuilder->buildAdvancedSection($form, $config);

    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   *
   * @param array<string, mixed> $form
   *   Form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state.
   */
  #[\Override]
  public function validateForm(array &$form, FormStateInterface $form_state): void {
    parent::validateForm($form, $form_state);

    $ffmpegPath = $form_state->getValue('ffmpeg_path');
    if (!empty($ffmpegPath) && !is_executable($ffmpegPath)) {
      $form_state->setErrorByName('ffmpeg_path', $this->t('FFmpeg path is not executable.'));
    }

    $ffprobePath = $form_state->getValue('ffprobe_path');
    if (!empty($ffprobePath) && !is_executable($ffprobePath)) {
      $form_state->setErrorByName('ffprobe_path', $this->t('FFprobe path is not executable.'));
    }

    // Output directory must be a Drupal stream wrapper URI.
    // Absolute filesystem paths are rejected because Drupal's
    // FileUrlGenerator and file access system cannot reliably serve
    // files outside of registered stream wrappers.
    $outputDir = trim((string) $form_state->getValue('output_directory'));
    if ($outputDir !== '') {
      if (!str_contains($outputDir, '://')) {
        $form_state->setErrorByName('output_directory', $this->t('Output directory must be a stream wrapper URI (e.g., <em>public://ffmpeg_media</em> or <em>private://ffmpeg_media</em>). Absolute filesystem paths are not supported.'));
      }
      else {
        $scheme = explode('://', $outputDir, 2)[0];
        if (!$this->streamWrapperManager->getViaScheme($scheme)) {
          $form_state->setErrorByName('output_directory', $this->t('The stream wrapper @scheme:// is not available. Use public:// or configure the @scheme file system.', [
            '@scheme' => $scheme,
          ]));
        }
      }
    }

    $maxFileSizeBytes = (int) $form_state->getValue('max_file_size') * 1024 * 1024;
    $largeFileThreshold = (int) $form_state->getValue('large_file_threshold');
    if ($maxFileSizeBytes > 0 && $largeFileThreshold > $maxFileSizeBytes) {
      $form_state->setErrorByName('large_file_threshold', $this->t(
        'Large file threshold must not exceed the maximum file size (@max MB).',
        ['@max' => (int) $form_state->getValue('max_file_size')],
      ));
    }

    if ($form_state->getValue('preserve_original')) {
      return;
    }

    if ($form_state->getValue('original_retention_enabled')) {
      $days = (int) $form_state->getValue('original_retention_days');
      if ($days < 1 || $days > 90) {
        $form_state->setErrorByName('original_retention_days', $this->t('Retention days must be between 1 and 90.'));
      }
    }

    if ($form_state->getValue('original_trash_enabled')) {
      $trashPath = trim((string) $form_state->getValue('original_trash_path'));
      if ($trashPath === '') {
        $form_state->setErrorByName('original_trash_path', $this->t('Trash directory is required when trash is enabled.'));
      }
      elseif (str_contains($trashPath, '..')) {
        $form_state->setErrorByName('original_trash_path', $this->t('Trash directory must not contain path traversal sequences.'));
      }
      elseif (!str_contains($trashPath, '://') && !str_starts_with($trashPath, DIRECTORY_SEPARATOR)) {
        $form_state->setErrorByName('original_trash_path', $this->t('Trash directory must be an absolute path or stream wrapper URI.'));
      }
      elseif (str_contains($trashPath, '://')) {
        $scheme = explode('://', $trashPath, 2)[0];
        $streamWrapperManager = $this->streamWrapperManager;
        if (!$streamWrapperManager->getViaScheme($scheme)) {
          $form_state->setErrorByName('original_trash_path', $this->t('The stream wrapper @scheme:// is not available. Use public:// or configure the @scheme file system.', [
            '@scheme' => $scheme,
          ]));
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   *
   * @param array<string, mixed> $form
   *   Form structure.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   Form state.
   */
  #[\Override]
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    // Filter enabled processors to only include checked values.
    $enabledProcessors = $form_state->getValue('enabled_processors')
      |> array_filter(...)
      |> array_values(...);
    $this->config('ffmpeg_media.settings')
      // General settings.
      ->set('enabled', (bool) $form_state->getValue('enabled'))
      ->set('default_profile', $form_state->getValue('default_profile'))
      ->set('output_directory', $form_state->getValue('output_directory'))
      ->set('max_file_size', (int) $form_state->getValue('max_file_size') * 1024 * 1024)
      ->set('preserve_original', (bool) $form_state->getValue('preserve_original'))
      ->set('original_retention_enabled', (bool) $form_state->getValue('original_retention_enabled'))
      ->set('original_retention_days', (int) $form_state->getValue('original_retention_days'))
      ->set('original_trash_enabled', (bool) $form_state->getValue('original_trash_enabled'))
      ->set('original_trash_path', $form_state->getValue('original_trash_path'))
      ->set('auto_cleanup_failed', (bool) $form_state->getValue('auto_cleanup_failed'))
      ->set('cleanup_age', (int) ($form_state->getValue('cleanup_age') ?? 86400))
      // FFmpeg settings.
      ->set('ffmpeg_path', $form_state->getValue('ffmpeg_path'))
      ->set('ffprobe_path', $form_state->getValue('ffprobe_path'))
      ->set('ffprobe_cache_ttl', (int) $form_state->getValue('ffprobe_cache_ttl'))
      ->set('ffmpeg_timeout', (int) $form_state->getValue('ffmpeg_timeout'))
      ->set('progress_update_interval', (int) $form_state->getValue('progress_update_interval'))
      ->set('progress_throttle_interval', (int) $form_state->getValue('progress_throttle_interval'))
      ->set('active_count_cache_ttl', (float) $form_state->getValue('active_count_cache_ttl'))
      // Queue settings.
      ->set('queue_claim_timeout', (int) $form_state->getValue('queue_claim_timeout'))
      ->set('large_file_threshold', (int) $form_state->getValue('large_file_threshold'))
      ->set('max_concurrent', (int) $form_state->getValue('max_concurrent'))
      ->set('lock_timeout', (int) $form_state->getValue('lock_timeout'))
      ->set('queue_verbose_logging', (bool) $form_state->getValue('queue_verbose_logging'))
      // Hardware acceleration.
      ->set('hardware_acceleration', $form_state->getValue('hardware_acceleration'))
      ->set('hardware_fallback_enabled', (bool) $form_state->getValue('hardware_fallback_enabled'))
      // Processors.
      ->set('enabled_processors', $enabledProcessors)
      // Manifest settings.
      ->set('manifest_enabled', (bool) $form_state->getValue('manifest_enabled'))
      ->set('manifest_format', $form_state->getValue('manifest_format'))
      ->set('manifest_include_checksums', (bool) $form_state->getValue('manifest_include_checksums'))
      ->set('manifest_include_sizes', (bool) $form_state->getValue('manifest_include_sizes'))
      // Audio settings.
      ->set('audio.normalize.mode', $form_state->getValue('audio_normalize_mode'))
      ->set('audio.normalize.target_lufs', (float) $form_state->getValue('audio_normalize_target_lufs'))
      ->set('audio.normalize.true_peak', (float) $form_state->getValue('audio_normalize_true_peak'))
      ->set('audio.normalize.lra', (float) $form_state->getValue('audio_normalize_lra'))
      ->set('audio.format.sample_rate', (int) $form_state->getValue('audio_sample_rate'))
      ->set('audio.format.channels', (int) $form_state->getValue('audio_channels'))
      ->set('audio.format.codec', $form_state->getValue('audio_codec'))
      ->set('audio.format.bitrate', $form_state->getValue('audio_bitrate'))
      // Retry settings.
      ->set('retry.enabled', (bool) $form_state->getValue('retry_enabled'))
      ->set('retry.max_retries', (int) $form_state->getValue('retry_max_retries'))
      ->set('retry.base_delay', (int) $form_state->getValue('retry_base_delay'))
      ->set('retry.max_delay', (int) $form_state->getValue('retry_max_delay'))
      // Circuit breaker settings.
      ->set('circuit_breaker.failure_threshold', (int) $form_state->getValue('circuit_breaker_failure_threshold'))
      ->set('circuit_breaker.success_threshold', (int) $form_state->getValue('circuit_breaker_success_threshold'))
      ->set('circuit_breaker.timeout', (int) $form_state->getValue('circuit_breaker_timeout'))
      // Security settings.
      ->set('health_probe_token_required', (bool) $form_state->getValue('health_probe_token_required'))
      ->set('rate_limit_requests', (int) $form_state->getValue('rate_limit_requests'))
      ->set('rate_limit_window', (int) $form_state->getValue('rate_limit_window'))
      // Advanced settings.
      ->set('stuck_process_timeout', (int) $form_state->getValue('stuck_process_timeout'))
      ->set('stuck_auto_requeue', (bool) $form_state->getValue('stuck_auto_requeue'))
      ->set('auto_requeue_batch_limit', (int) $form_state->getValue('auto_requeue_batch_limit'))
      ->set('segment_cleanup_age', (int) $form_state->getValue('segment_cleanup_age'))
      ->save();

    // Store health probe token in State API (non-exportable secret).
    // Password fields submit empty when unchanged — only update when
    // the user explicitly provides a new value.
    $token = trim((string) $form_state->getValue('health_probe_token'));
    if ($token !== '') {
      $this->state->set('ffmpeg_media.health_probe_token', $token);
    }

    parent::submitForm($form, $form_state);
  }

}
