<?php

declare(strict_types=1);

namespace Drupal\Tests\acquia_dam\Kernel;

use Drupal\acquia_dam\Entity\ManagedFileField;
use Drupal\acquia_dam\Entity\MediaSourceField;
use Drupal\Core\Url;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media\MediaTypeInterface;
use Drupal\responsive_image\Entity\ResponsiveImageStyle;
use Symfony\Component\HttpFoundation\Request;

/**
 * Tests the Asset media source plugin.
 *
 * @group acquia_dam_kernel
 */
class AssetTest extends AcquiaDamKernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'responsive_image',
    'breakpoint',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installConfig(['system', 'media', 'media_library', 'acquia_dam']);

    $this->config('acquia_dam.settings')
      ->set('allowed_image_styles', ['large'])
      ->save();

    // Create test responsive image style.
    $this->createResponsiveImageStyle();
  }

  /**
   * Test that media types have the correct field configuration.
   *
   * Ensure default media types have the correct field configuration initially
   * and updates the view display correctly when download_sync is enabled.
   */
  public function testDefaultFieldConfiguration(): void {
    // Get all the default media types.
    /** @var \Drupal\media\MediaTypeInterface[] $acquia_dam_media_types */
    $acquia_dam_media_types = $this->entityTypeManager->getStorage('media_type')->loadMultiple();
    foreach ($acquia_dam_media_types as $media_type_id => $media_type) {
      // Validate that the active field matches configuration.
      $expected_field = $media_type->getSource()->getActiveFieldName();
      $entity_view_display = $this->entityTypeManager->getStorage('entity_view_display')->load('media.' . $media_type_id . '.default');
      $this->assertArrayHasKey($expected_field, $entity_view_display->get('content'));
      $this->assertArrayNotHasKey($expected_field, $entity_view_display->get('hidden'));

      // Test that the toggle function works with new media types.
      $this->testDownloadSyncToggle($media_type_id, $media_type);
    }
  }

  /**
   * Test a newly created media type matches the expected configuration.
   *
   * @param string $source_plugin_id
   *   The source plugin ID.
   * @param array $source_config
   *   The source configuration.
   * @param array $expected_config
   *   The expected field configuration.
   *
   * @dataProvider dataProviderDamMediaSourceFields
   */
  public function testNewMediaTypeDefaultFieldConfiguration(string $source_plugin_id, array $source_config, array $expected_config): void {
    // Create the media type with the specified source configuration.
    $media_type = $this->createMediaType($source_plugin_id, $source_config);

    // Test that the default field matches what exists within the view display.
    $expected_field = $media_type->getSource()->getActiveFieldName();
    $entity_view_display = $this->entityTypeManager->getStorage('entity_view_display')->load('media.' . $media_type->id() . '.default');
    $actual_display = $entity_view_display->get('content');
    // Remove weight as its dynamically set and we don't care about it.
    unset($actual_display[$expected_field]['weight']);
    $this->assertArrayHasKey($expected_field, $actual_display);
    $this->assertArrayNotHasKey($expected_field, $entity_view_display->get('hidden'));
    $this->assertEquals($expected_config, $actual_display[$expected_field]);

    // Validate uri_scheme configuration when download_assets is enabled.
    if (!empty($source_config['download_assets'])) {
      $actual_source_config = $media_type->getSource()->getConfiguration();

      // Check if uri_scheme was provided in source config.
      if (isset($source_config['uri_scheme'])) {
        // Verify uri_scheme matches what was provided.
        $this->assertArrayHasKey('uri_scheme', $actual_source_config, 'uri_scheme should be set in source configuration when download_assets is TRUE');
        $this->assertSame($source_config['uri_scheme'], $actual_source_config['uri_scheme'], 'uri_scheme should match the provided value');

        // Verify getUriScheme() method returns the same value.
        $this->assertSame($source_config['uri_scheme'], $media_type->getSource()->getUriScheme(), 'getUriScheme() should return the configured uri_scheme');
      }
      else {
        // If uri_scheme not provided, should default to system default_scheme.
        $this->assertArrayHasKey('uri_scheme', $actual_source_config, 'uri_scheme should be set even when not explicitly provided');
        $system_default = $this->config('system.file')->get('default_scheme') ?? 'public';
        $this->assertSame($system_default, $actual_source_config['uri_scheme'], 'uri_scheme should default to system default_scheme');
      }
    }

    // Test that the toggle function works with new media types.
    $this->testDownloadSyncToggle($media_type->id(), $media_type);
  }

  /**
   * Test that the image style is swapped correctly.
   *
   * @param array $existing_config
   *   The existing configuration.
   * @param array $expected_config
   *   The expected configuration.
   *
   * @dataProvider dataProviderImageTypeFields
   */
  public function testImageStyleSwap(array $existing_config, array $expected_config): void {
    $this->assertFormatterSwap($existing_config, $expected_config);
  }

  /**
   * Common test method for image formatter swapping.
   */
  private function assertFormatterSwap(array $existing_config, array $expected_config, string $source_plugin_id = 'acquia_dam_asset:image'): void {
    // Create the media type with the specified source configuration.
    $download_assets = $existing_config['type'] !== 'acquia_dam_embed_code';
    $media_type = $this->createMediaType($source_plugin_id, ['download_assets' => $download_assets]);

    // Configure the content display with existing formatter settings.
    $source = $media_type->getSource();
    $content_display = \Drupal::service('entity_display.repository')->getViewDisplay('media', $media_type->id(), 'default');
    $content_display->setComponent($source->getActiveFieldName(), $existing_config);
    $content_display->save();

    // Test download sync toggle behavior.
    $this->testDownloadSyncToggle($media_type->id(), $media_type);

    // Verify formatter configuration.
    $expected_field = $media_type->getSource()->getActiveFieldName();
    $entity_view_display = $this->entityTypeManager->getStorage('entity_view_display')->load('media.' . $media_type->id() . '.default');
    $actual_display = $entity_view_display->get('content');
    unset($actual_display[$expected_field]['weight']);
    $this->assertEquals($expected_config, $actual_display[$expected_field]);
  }

  /**
   * Create a media type for a source plugin.
   *
   * @param string $source_plugin_id
   *   The media source plugin ID.
   * @param mixed[] $configuration
   *   (optional) Additional values for the media type entity:
   *   - id: The ID of the media type. If none is provided, a random value will
   *     be used.
   *   - label: The human-readable label of the media type. If none is provided,
   *     a random value will be used.
   *   See \Drupal\media\MediaTypeInterface and \Drupal\media\Entity\MediaType
   *   for full documentation of the media type properties.
   *
   * @return \Drupal\media\MediaTypeInterface
   *   A media type.
   *
   * @see \Drupal\media\MediaTypeInterface
   * @see \Drupal\media\Entity\MediaType
   */
  private function createMediaType(string $source_plugin_id, array $configuration = []): MediaTypeInterface {
    $values = [
      'id' => $this->randomMachineName(),
      'label' => $this->randomString(),
      'source' => $source_plugin_id,
    ];

    /** @var \Drupal\media\MediaTypeInterface $media_type */
    $media_type = MediaType::create($values);

    $source = $media_type->getSource();
    $source_field = $source->createSourceField($media_type);
    $source_configuration = array_merge($source->getConfiguration(), $configuration);
    $source_configuration['source_field'] = $source_field->getName();
    $source->setConfiguration($source_configuration);

    $this->assertSame(SAVED_NEW, $media_type->save());

    // Add the source field to the form display for the media type.
    $form_display = \Drupal::service('entity_display.repository')->getFormDisplay('media', $media_type->id(), 'default');
    $source->prepareFormDisplay($media_type, $form_display);
    $form_display->save();

    // Add the source field to the content display for the media type.
    $content_display = \Drupal::service('entity_display.repository')->getViewDisplay('media', $media_type->id(), 'default');
    $source->prepareViewDisplay($media_type, $content_display);
    $content_display->save();

    return $media_type;
  }

  /**
   * Helper method to toggle download/sync functionality and field swapping.
   *
   * @param string $media_type_id
   *   The Media Type ID.
   * @param \Drupal\media\MediaTypeInterface $media_type
   *   The media type object.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  private function testDownloadSyncToggle(string $media_type_id, MediaTypeInterface $media_type): void {
    // Store the existing active field.
    $expected_field = $media_type->getSource()->getActiveFieldName();

    // Toggle the download assets option.
    $configuration = $media_type->getSource()->getConfiguration();
    $configuration['download_assets'] = !$configuration['download_assets'];
    $media_type->getSource()->setConfiguration($configuration);
    $media_type->save();

    // Test that the new configuration is different from original.
    $media_type = $this->entityTypeManager->getStorage('media_type')->load($media_type_id);
    $new_expected_field = $media_type->getSource()->getActiveFieldName();
    $this->assertNotEquals($expected_field, $new_expected_field);
    $entity_view_display = $this->entityTypeManager->getStorage('entity_view_display')->load('media.' . $media_type_id . '.default');
    $this->assertArrayHasKey($new_expected_field, $entity_view_display->get('content'));
    $this->assertArrayNotHasKey($expected_field, $entity_view_display->get('content'));
    $this->assertArrayHasKey($expected_field, $entity_view_display->get('hidden'));
    $this->assertArrayNotHasKey($new_expected_field, $entity_view_display->get('hidden'));
  }

  /**
   * Data provider for Asset Tests.
   *
   * @return \Generator
   *   The data.
   */
  public static function dataProviderDamMediaSourceFields(): \Generator {
    yield [
      'acquia_dam_asset:archive',
      ['download_assets' => FALSE],
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'link_download',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:archive',
      ['download_assets' => TRUE],
      [
        'type' => 'file_default',
        'label' => 'hidden',
        'settings' => ['use_description_as_link_text' => TRUE],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:audio',
      ['download_assets' => FALSE],
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'remote_streaming',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:audio',
      [
        'download_assets' => TRUE,
        'uri_scheme' => 'public',
      ],
      [
        'type' => 'file_audio',
        'label' => 'hidden',
        'settings' => [
          'controls' => TRUE,
          'autoplay' => FALSE,
          'loop' => FALSE,
          'multiple_file_display_type' => 'tags',
        ],
        'third_party_settings' => [],
        'region' => 'content',
        'file_extensions' => 'mp3 ogg wav',
      ],
    ];
    yield [
      'acquia_dam_asset:audio',
      [
        'download_assets' => TRUE,
        'uri_scheme' => 'private',
      ],
      [
        'type' => 'file_audio',
        'label' => 'hidden',
        'settings' => [
          'controls' => TRUE,
          'autoplay' => FALSE,
          'loop' => FALSE,
          'multiple_file_display_type' => 'tags',
        ],
        'third_party_settings' => [],
        'region' => 'content',
        'file_extensions' => 'mp3 ogg wav',
      ],
    ];
    yield [
      'acquia_dam_asset:documents',
      ['download_assets' => FALSE],
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'original',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:documents',
      [
        'download_assets' => TRUE,
        'uri_scheme' => 'public',
      ],
      [
        'type' => 'file_default',
        'label' => 'hidden',
        'settings' => ['use_description_as_link_text' => TRUE],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:documents',
      [
        'download_assets' => TRUE,
        'uri_scheme' => 'private',
      ],
      [
        'type' => 'file_default',
        'label' => 'hidden',
        'settings' => ['use_description_as_link_text' => TRUE],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:generic',
      ['download_assets' => FALSE],
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'link_download',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:generic',
      ['download_assets' => TRUE],
      [
        'type' => 'file_default',
        'label' => 'hidden',
        'settings' => ['use_description_as_link_text' => TRUE],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:image',
      ['download_assets' => FALSE],
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'original',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:image',
      [
        'download_assets' => TRUE,
        'uri_scheme' => 'public',
      ],
      [
        'type' => 'image',
        'label' => 'hidden',
        'settings' => [
          'image_link' => '',
          'image_style' => '',
          'image_loading' => ['attribute' => 'lazy'],
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:image',
      [
        'download_assets' => TRUE,
        'uri_scheme' => 'private',
      ],
      [
        'type' => 'image',
        'label' => 'hidden',
        'settings' => [
          'image_link' => '',
          'image_style' => '',
          'image_loading' => ['attribute' => 'lazy'],
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:pdf',
      ['download_assets' => FALSE],
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'link_thumbnail_download',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:pdf',
      [
        'download_assets' => TRUE,
        'uri_scheme' => 'public',
      ],
      [
        'type' => 'file_default',
        'label' => 'hidden',
        'settings' => ['use_description_as_link_text' => TRUE],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:pdf',
      [
        'download_assets' => TRUE,
        'uri_scheme' => 'private',
      ],
      [
        'type' => 'file_default',
        'label' => 'hidden',
        'settings' => ['use_description_as_link_text' => TRUE],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:spinset',
      ['download_assets' => FALSE],
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'link_text',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:spinset',
      ['download_assets' => TRUE],
      [
        'type' => 'file_default',
        'label' => 'hidden',
        'settings' => ['use_description_as_link_text' => TRUE],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:video',
      ['download_assets' => FALSE],
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'inline_view',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      'acquia_dam_asset:video',
      [
        'download_assets' => TRUE,
        'uri_scheme' => 'public',
      ],
      [
        'type' => 'file_video',
        'label' => 'hidden',
        'settings' => [
          'controls' => TRUE,
          'autoplay' => FALSE,
          'loop' => FALSE,
          'multiple_file_display_type' => 'tags',
          'muted' => FALSE,
          'width' => 640,
          'height' => 480,
        ],
        'third_party_settings' => [],
        'region' => 'content',
        'file_extensions' => 'mp4 mov',
      ],
    ];
    yield [
      'acquia_dam_asset:video',
      [
        'download_assets' => TRUE,
        'uri_scheme' => 'private',
      ],
      [
        'type' => 'file_video',
        'label' => 'hidden',
        'settings' => [
          'controls' => TRUE,
          'autoplay' => FALSE,
          'loop' => FALSE,
          'multiple_file_display_type' => 'tags',
          'muted' => FALSE,
          'width' => 640,
          'height' => 480,
        ],
        'third_party_settings' => [],
        'region' => 'content',
        'file_extensions' => 'mp4 mov',
      ],
    ];
  }

  /**
   * Data provider for Image Asset  Tests.
   *
   * @return \Generator
   *   The data.
   */
  public static function dataProviderImageTypeFields(): \Generator {
    yield [
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'original',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
      [
        'type' => 'image',
        'label' => 'hidden',
        'settings' => [
          'image_link' => '',
          'image_style' => '',
          'image_loading' => ['attribute' => 'lazy'],
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      [
        'type' => 'image',
        'label' => 'hidden',
        'settings' => [
          'image_link' => '',
          'image_style' => '',
          'image_loading' => ['attribute' => 'lazy'],
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'original',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'large',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
      [
        'type' => 'image',
        'label' => 'hidden',
        'settings' => [
          'image_link' => '',
          'image_style' => 'large',
          'image_loading' => ['attribute' => 'lazy'],
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      [
        'type' => 'image',
        'label' => 'hidden',
        'settings' => [
          'image_link' => '',
          'image_style' => 'large',
          'image_loading' => ['attribute' => 'lazy'],
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'large',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
  }

  /**
   * Test that the image formatter is swapped correctly.
   *
   * @param array $existing_config
   *   The existing configuration.
   * @param array $expected_config
   *   The expected configuration.
   *
   * @dataProvider dataProviderImageResponsiveFormatSwap
   */
  public function testImageResponsiveFormatSwap(array $existing_config, array $expected_config): void {
    $this->assertFormatterSwap($existing_config, $expected_config);
  }

  /**
   * Data provider for responsive image format.
   *
   * @return \Generator
   *   The data.
   */
  public static function dataProviderImageResponsiveFormatSwap(): \Generator {
    yield [
      [
        'type' => 'acquia_dam_responsive_image',
        'label' => 'hidden',
        'settings' => [
          'responsive_image_style' => 'test_style',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
      [
        'type' => 'responsive_image',
        'label' => 'hidden',
        'settings' => [
          'responsive_image_style' => 'test_style',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      [
        'type' => 'responsive_image',
        'label' => 'hidden',
        'settings' => [
          'responsive_image_style' => 'test_style',
          'image_link' => '',
          'image_loading' => ['attribute' => 'lazy'],
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
      [
        'type' => 'acquia_dam_responsive_image',
        'label' => 'hidden',
        'settings' => [
          'responsive_image_style' => 'test_style',
          'image_link' => '',
          'image_loading' => ['attribute' => 'lazy'],
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'large',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
      [
        'type' => 'image',
        'label' => 'hidden',
        'settings' => [
          'image_link' => '',
          'image_style' => 'large',
          'image_loading' => ['attribute' => 'lazy'],
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
    yield [
      [
        'type' => 'image',
        'label' => 'hidden',
        'settings' => [
          'image_link' => '',
          'image_style' => 'large',
          'image_loading' => ['attribute' => 'lazy'],
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
      [
        'type' => 'acquia_dam_embed_code',
        'label' => 'hidden',
        'settings' => [
          'embed_style' => 'large',
        ],
        'third_party_settings' => [],
        'region' => 'content',
      ],
    ];
  }

  /**
   * Tests that uri_scheme cannot be changed via form when media items exist.
   *
   * This test verifies the form validation that prevents changing file storage
   * location after media entities have been created.
   */
  public function testFileSystemSwapFormValidation(): void {
    // Create a media type with download_assets enabled and
    // uri_scheme set to private.
    $media_type = $this->createMediaType('acquia_dam_asset:image', [
      'download_assets' => TRUE,
      'uri_scheme' => 'private',
    ]);

    // Set up user with proper permissions for form submission.
    $this->drupalSetUpCurrentUser([], [
      'access content',
      'administer media types',
    ]);

    $url = Url::fromRoute('entity.media_type.edit_form', [
      'media_type' => $media_type->id(),
    ]);

    // First, change uri_scheme to public before creating media (should work).
    $this->assertUriSchemeChange($media_type, $url, 'public', TRUE, 'Form submission should succeed when no media items exist');
    $this->assertUriSchemeEquals($media_type, 'public', 'uri_scheme should be changed to public when no media exists');

    // Create a media entity to prevent further uri_scheme changes.
    $media = Media::create([
      'bundle' => $media_type->id(),
      MediaSourceField::SOURCE_FIELD_NAME => [
        'asset_id' => 'f2a9c03d-3664-477c-8013-e84504ed5adc',
        'version_id' => '77b3dd66-e590-4259-82db-25e7f4e62772',
        'external_id' => 'm2x3z2j9ou',
      ],
    ]);
    $media->save();

    // Verify the uri_scheme field is disabled when media items exist.
    $this->processRequest(Request::create($url->toString()));
    $uri_scheme_field = $this->cssSelect('select[name="source_configuration[uri_scheme]"]');
    if (!empty($uri_scheme_field)) {
      $disabled_attribute = $uri_scheme_field[0]->attributes()->disabled ?? NULL;
      $this->assertNotNull($disabled_attribute, 'uri_scheme field should be disabled when media items exist');
    }

    // Attempt to change uri_scheme to private (should be ignored).
    $this->assertUriSchemeChange($media_type, $url, 'private', TRUE, 'Form submission should complete even when uri_scheme is locked');
    $this->assertUriSchemeEquals($media_type, 'public', 'uri_scheme should remain locked at public when media items exist');
  }

  /**
   * Helper method to assert uri_scheme changes via form submission.
   */
  private function assertUriSchemeChange(MediaTypeInterface $media_type, Url $url, string $new_scheme, bool $should_succeed, string $message): void {
    $this->processRequest(Request::create($url->toString()));

    $form_data = [
      'label' => $media_type->label(),
      'id' => $media_type->id(),
      'source_configuration' => [
        'source_field' => MediaSourceField::SOURCE_FIELD_NAME,
        'download_assets' => TRUE,
        'uri_scheme' => $new_scheme,
      ],
    ];

    $response = $this->doFormSubmit($url->toString(), $form_data);
    $expected_status = $should_succeed ? 303 : 200;
    $this->assertEquals($expected_status, $response->getStatusCode(), $message);
  }

  /**
   * Helper method to assert the current uri_scheme configuration.
   */
  private function assertUriSchemeEquals(MediaTypeInterface $media_type, string $expected_scheme, string $message): void {
    $this->entityTypeManager->getStorage('media_type')->resetCache();
    $reloaded_media_type = $this->entityTypeManager->getStorage('media_type')->load($media_type->id());
    $configuration = $reloaded_media_type->getSource()->getConfiguration();
    $this->assertEquals($expected_scheme, $configuration['uri_scheme'], $message);
  }

  /**
   * Creates a test responsive image style.
   */
  protected function createResponsiveImageStyle(): void {
    // Install the configuration for breakpoints.
    $this->installConfig(['breakpoint', 'responsive_image']);

    // Create an image style that will be used by the responsive image style.
    $image_style = $this->container->get('entity_type.manager')
      ->getStorage('image_style')
      ->create([
        'name' => 'test_style',
        'label' => 'Test Style',
      ]);
    $image_style->save();

    // Create a responsive image style for testing.
    $responsive_image_style = ResponsiveImageStyle::create([
      'id' => 'test_style',
      'label' => 'Test style',
      'breakpoint_group' => 'responsive_image',
      'fallback_image_style' => 'test_style',
    ]);

    // Add a mapping for the responsive image style.
    $responsive_image_style->addImageStyleMapping('responsive_image.viewport_sizing', '1x', [
      'image_mapping_type' => 'image_style',
      'image_mapping' => 'test_style',
    ]);

    $responsive_image_style->save();
  }

  /**
   * Tests that Video and Audio source configurations are seeded with the
   * correct file_extensions from the derivative plugin at creation time.
   *
   * Regression coverage: Asset::defaultConfiguration() previously called
   * $this->getFileExtensions() before configuration was initialised, always
   * returning '' regardless of the derivative plugin's declared extensions.
   *
   * @param string $source_plugin_id
   *   The source plugin ID.
   * @param string $expected_extensions
   *   The expected file extensions in source configuration.
   *
   * @dataProvider dataProviderFileExtensions
   */
  public function testFileExtensionsInSourceConfiguration(string $source_plugin_id, string $expected_extensions): void {
    $media_type = $this->createMediaType($source_plugin_id, ['download_assets' => TRUE]);
    $configuration = $media_type->getSource()->getConfiguration();

    $this->assertArrayHasKey(
      'file_extensions',
      $configuration,
      "Source configuration for $source_plugin_id must contain a 'file_extensions' key."
    );
    $this->assertSame(
      $expected_extensions,
      $configuration['file_extensions'],
      "Source configuration for $source_plugin_id must have file_extensions '$expected_extensions'."
    );
  }

  /**
   * Tests that getFileExtensions() falls back to the derivative plugin when
   * the stored configuration value is an empty string.
   *
   * This covers existing media types created before the fix where '' was stored
   * in source config for file_extensions.
   *
   * @param string $source_plugin_id
   *   The source plugin ID.
   * @param string $expected_extensions
   *   The expected extensions returned by getFileExtensions().
   *
   * @dataProvider dataProviderFileExtensions
   */
  public function testFileExtensionsFallbackForExistingMediaType(string $source_plugin_id, string $expected_extensions): void {
    $media_type = $this->createMediaType($source_plugin_id, ['download_assets' => TRUE]);
    $source = $media_type->getSource();

    // Simulate the pre-fix state: force file_extensions to '' to mimic a
    // media type whose source config was saved before this fix.
    $configuration = $source->getConfiguration();
    $configuration['file_extensions'] = '';
    $source->setConfiguration($configuration);

    $this->assertSame(
      $expected_extensions,
      $source->getFileExtensions(),
      "getFileExtensions() must fall back to the derivative plugin default for $source_plugin_id even when '' is stored."
    );
  }

  /**
   * Tests that ManagedFileField carries the correct file_extensions setting
   * on the bundle field definition, enabling the file_video / file_audio
   * formatter to be available for selection.
   *
   * @param string $source_plugin_id
   *   The source plugin ID.
   * @param string $expected_extensions
   *   The expected file extensions on the bundle field.
   *
   * @dataProvider dataProviderFileExtensions
   */
  public function testManagedFileFieldCarriesFileExtensions(string $source_plugin_id, string $expected_extensions): void {
    $media_type = $this->createMediaType($source_plugin_id, ['download_assets' => TRUE]);

    $field_definitions = $this->container
      ->get('entity_field.manager')
      ->getFieldDefinitions('media', $media_type->id());

    $this->assertArrayHasKey(
      ManagedFileField::MANAGED_FILE_FIELD_NAME,
      $field_definitions,
      "The managed file field must be registered on the $source_plugin_id bundle."
    );

    $actual_extensions = $field_definitions[ManagedFileField::MANAGED_FILE_FIELD_NAME]
      ->getSetting('file_extensions');

    $this->assertSame(
      $expected_extensions,
      $actual_extensions,
      "ManagedFileField on $source_plugin_id must carry file_extensions '$expected_extensions' so the formatter is available."
    );
  }

  /**
   * Data provider for file extension tests.
   *
   * Covers the two derivative plugins that declare file_extensions in their
   * formatter configuration for the managed file field.
   *
   * @return \Generator
   *   Yields [source_plugin_id, expected_file_extensions].
   */
  public static function dataProviderFileExtensions(): \Generator {
    yield 'video has mp4 and mov' => [
      'acquia_dam_asset:video',
      'mp4 mov',
    ];
    yield 'audio has mp3, ogg and wav' => [
      'acquia_dam_asset:audio',
      'mp3 ogg wav',
    ];
  }

}
