<?php

declare(strict_types=1);

namespace Drupal\Tests\acquia_dam\Kernel;

use Drupal\acquia_dam\Entity\MediaSourceField;
use Drupal\acquia_dam\Plugin\Field\FieldType\AssetItem;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\file\FileInterface;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media\MediaInterface;

/**
 * Media integration tests.
 *
 * @group acquia_dam_kernel
 */
final class MediaIntegrationTest extends AcquiaDamKernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->setSetting('file_private_path', $this->container->getParameter('site.path') . '/private');
  }

  /**
   * {@inheritdoc}
   */
  public function register(ContainerBuilder $container): void {
    parent::register($container);
    $container->register('stream_wrapper.private', 'Drupal\Core\StreamWrapper\PrivateStream')
      ->addTag('stream_wrapper', ['scheme' => 'private']);
  }

  /**
   * Tests the configuration install for the module.
   */
  public function testInstall(): void {
    $this->installConfig([
      'image',
      'media',
      'media_library',
      'acquia_dam',
    ]);
    self::assertNotNull(MediaType::load('acquia_dam_audio_asset'));
    self::assertNotNull(MediaType::load('acquia_dam_generic_asset'));
    self::assertNotNull(MediaType::load('acquia_dam_pdf_asset'));
    self::assertNotNull(MediaType::load('acquia_dam_video_asset'));
  }

  /**
   * Tests the media source plugin.
   */
  public function testMediaSource(): void {
    $this->drupalSetUpCurrentUser();
    $this->setDamSiteToken();

    $media_source_manager = $this->container->get('plugin.manager.media.source');
    self::assertTrue($media_source_manager->hasDefinition('acquia_dam_asset:audio'));
    self::assertTrue($media_source_manager->hasDefinition('acquia_dam_asset:generic'));
    self::assertTrue($media_source_manager->hasDefinition('acquia_dam_asset:pdf'));
    self::assertTrue($media_source_manager->hasDefinition('acquia_dam_asset:video'));

    $media_type = $this->createPdfMediaType();
    $media = Media::create([
      'bundle' => $media_type->id(),
      'acquia_dam_asset_id' => [
        'asset_id' => '0324b0b2-5293-4aa0-b0aa-c85b003395e2',
        'version_id' => 'bd7a95eb-17a4-48b6-88cf-5443739e3683',
        'external_id' => '8a1ouvfchk',
      ],
    ]);
    $media->save();
    assert($media instanceof MediaInterface);
    self::assertEquals('0324b0b2-5293-4aa0-b0aa-c85b003395e2', $media->get('acquia_dam_asset_id')->asset_id);
    self::assertEquals('8a1ouvfchk', $media->get('acquia_dam_asset_id')->external_id);
    self::assertEquals(
      'public://acquia_dam_thumbnails/0324b0b2-5293-4aa0-b0aa-c85b003395e2/bd7a95eb-17a4-48b6-88cf-5443739e3683.png',
      $media_type->getSource()->getMetadata($media, 'thumbnail_uri')
    );
    self::assertFileExists($media_type->getSource()->getMetadata($media, 'thumbnail_uri'));
  }

  /**
   * Tests that a version ID is updated when a media entity is inserted.
   */
  public function testMediaPreSave(): void {
    $media_type = $this->createVideoMediaType();
    $media = Media::create([
      'bundle' => $media_type->id(),
      'acquia_dam_asset_id' => [
        'asset_id' => 'efb03f75-3c42-497b-baa9-5ec79d1f56af',
        'version_id' => '',
        'external_id' => '',
      ],
    ]);
    $media->save();
    assert($media instanceof MediaInterface);
    $source_field_item = $media->get(MediaSourceField::SOURCE_FIELD_NAME)->first();
    assert($source_field_item instanceof AssetItem);
    self::assertEquals('efb03f75-3c42-497b-baa9-5ec79d1f56af', $source_field_item->asset_id);
    self::assertEquals('4f656c07-6a08-47b3-9403-16082d2fcda3', $source_field_item->version_id);
    self::assertEquals('mnmc58hipn', $source_field_item->external_id);
    $revision_id = $media->getRevisionId();

    $media->setNewRevision();
    $source_field_item->version_id = 'a_new_version';
    $media->save();
    $media = $this->reloadEntity($media);
    assert($media instanceof MediaInterface);
    $source_field_item = $media->get(MediaSourceField::SOURCE_FIELD_NAME)->first();
    assert($source_field_item instanceof AssetItem);
    self::assertEquals('efb03f75-3c42-497b-baa9-5ec79d1f56af', $source_field_item->asset_id);
    self::assertEquals('a_new_version', $source_field_item->version_id);

    $media_storage = $this->container->get('entity_type.manager')->getStorage('media');
    $media_revision = $media_storage->loadRevision($revision_id);
    assert($media_revision instanceof MediaInterface);
    $source_field_item = $media_revision->get(MediaSourceField::SOURCE_FIELD_NAME)->first();
    assert($source_field_item instanceof AssetItem);
    self::assertEquals('efb03f75-3c42-497b-baa9-5ec79d1f56af', $source_field_item->asset_id);
    self::assertEquals('4f656c07-6a08-47b3-9403-16082d2fcda3', $source_field_item->version_id);
  }

  /**
   * Tests that a thumbnail is mapped when a media entity is inserted.
   */
  public function testMediaThumbnails(): void {
    $media_type = $this->createVideoMediaType();
    $media = Media::create([
      'name' => 'SD-Social Promo_.mp4',
      'bundle' => $media_type->id(),
      'acquia_dam_asset_id' => [
        'asset_id' => 'efb03f75-3c42-497b-baa9-5ec79d1f56af',
        'version_id' => '',
        'external_id' => '',
      ],
    ]);
    self::assertTrue($media->get('thumbnail')->isEmpty());
    $media->save();
    self::assertFalse($media->get('thumbnail')->isEmpty());
    $thumbnail = $media->get('thumbnail')->entity;
    self::assertInstanceOf(FileInterface::class, $thumbnail);
    self::assertEquals(
      'public://acquia_dam_thumbnails/efb03f75-3c42-497b-baa9-5ec79d1f56af/4f656c07-6a08-47b3-9403-16082d2fcda3.png',
      $thumbnail->getFileUri()
    );
    $revision_id = $media->getRevisionId();

    $media->get(MediaSourceField::SOURCE_FIELD_NAME)->version_id = '4f656c07-6a08-47b3-9403-16082d2fcda3';
    $media->setNewRevision(TRUE);
    $media->updateQueuedThumbnail();
    $media->save();
    $media = $this->reloadEntity($media);
    $thumbnail = $media->get('thumbnail')->entity;
    self::assertInstanceOf(FileInterface::class, $thumbnail);
    self::assertEquals(
      'public://acquia_dam_thumbnails/efb03f75-3c42-497b-baa9-5ec79d1f56af/4f656c07-6a08-47b3-9403-16082d2fcda3.png',
      $thumbnail->getFileUri()
    );

    $media_storage = $this->container->get('entity_type.manager')->getStorage('media');
    $media_revision = $media_storage->loadRevision($revision_id);
    assert($media_revision instanceof MediaInterface);
    self::assertEquals(
      'public://acquia_dam_thumbnails/efb03f75-3c42-497b-baa9-5ec79d1f56af/4f656c07-6a08-47b3-9403-16082d2fcda3.png',
      $media_revision->get('thumbnail')->entity->getFileUri()
    );
  }

  /**
   * Local asset copies.
   *
   * Tests whether the binary file of a media asset is being downloaded and
   * stored in the local file system.
   *
   * @covers Drupal\acquia_dam\AssetFileEntityHelper::downloadFile
   */
  public function testAssetFileDownload(): void {
    $media_type = $this->createImageMediaType(['download_assets' => TRUE]);
    self::assertTrue($media_type->get('source_configuration')['download_assets']);

    $filename = 'An asset with many versions.png';
    $media_entity = Media::create([
      'bundle' => $media_type->id(),
      'name' => $filename,
      'acquia_dam_asset_id' => [
        'asset_id' => 'f2a9c03d-3664-477c-8013-e84504ed5adc',
        'version_id' => 'e43bde3a-be80-418e-a69e-6de9285afbbf',
      ],
    ]);
    $media_entity->save();
    assert($media_entity instanceof MediaInterface);
    $filename = strtolower(preg_replace('/\s/', '-', $filename));
    self::assertFileExists("public://dam/asset_external_id/$filename", 'The asset file expected to be already downloaded does not exist.');
    self::assertFalse($media_entity->get('acquia_dam_managed_image')->isEmpty());
  }

  /**
   * Data provider for testAssetFileDownloadPdf.
   *
   * @return array<string, array{string}>
   */
  public static function pdfUriSchemeProvider(): array {
    return [
      'public scheme expects changed query param' => ['public'],
      'private scheme excludes changed query param' => ['private'],
    ];
  }

  /**
   * Tests that a PDF asset binary is downloaded and that LocalFileUrlGenerator
   * appends (public) or omits (private) the ?changed= query param.
   *
   * Covers the non-image (acquia_dam_managed_file) branch of
   * AssetFileEntityHelper::getFieldName(), which differs from the image test.
   *
   * @covers \Drupal\acquia_dam\AssetFileEntityHelper::downloadFile
   * @covers \Drupal\acquia_dam\Service\LocalFileUrlGenerator::generate
   *
   * @dataProvider pdfUriSchemeProvider
   */
  public function testAssetFileDownloadPdf1(string $uri_scheme): void {
    $media_type = $this->createPdfMediaType(['download_assets' => TRUE, 'uri_scheme' => $uri_scheme]);
    self::assertTrue($media_type->get('source_configuration')['download_assets']);
    self::assertEquals($uri_scheme, $media_type->get('source_configuration')['uri_scheme']);

    $media_entity = Media::create([
      'bundle' => $media_type->id(),
      'name'   => "Explorer owner's manual.pdf",
      'acquia_dam_asset_id' => [
        'asset_id'    => '0324b0b2-5293-4aa0-b0aa-c85b003395e2',
        'version_id'  => 'bd7a95eb-17a4-48b6-88cf-5443739e3683',
        'external_id' => '8a1ouvfchk',
      ],
    ]);
    $media_entity->save();
    assert($media_entity instanceof MediaInterface);

    // Assert the non-image managed file field is populated — this exercises
    // the else-branch of getFieldName().
    self::assertFalse(
      $media_entity->get('acquia_dam_managed_file')->isEmpty(),
      'acquia_dam_managed_file should be populated after a PDF download.'
    );

    $file_entity = $media_entity->get('acquia_dam_managed_file')->entity;
    self::assertInstanceOf(FileInterface::class, $file_entity);

    $file_uri = $file_entity->getFileUri();

    // Verify the file landed under the expected scheme directory.
    self::assertStringContainsString(
      $uri_scheme . '://dam/asset_external_id/',
      $file_uri,
      "Downloaded PDF should be stored under $uri_scheme://dam/asset_external_id/."
    );

    // Verify the physical file was written to the local filesystem.
    self::assertFileExists($file_uri, 'Downloaded PDF file does not exist on the local filesystem.');

    $url = $this->container->get('file_url_generator')->generate($file_uri);
    $options = $url->getOptions();

    $file_entities = \Drupal::entityTypeManager()->getStorage('file')->loadByProperties(['uri' => $file_uri]);
    $file = reset($file_entities);
    $this->assertNotEmpty($file, 'The file entity should exist.');

    self::assertArrayHasKey('query', $options, 'Generated URL should carry query options.');
    self::assertArrayHasKey('changed', $options['query'], 'LocalFileUrlGenerator should append ?changed= for DAM public files.');
    self::assertIsInt($options['query']['changed'], '?changed= value should be an integer (file changed timestamp).');
    self::assertGreaterThan(0, $options['query']['changed'], '?changed= timestamp should be a positive integer.');
    self::assertEquals($file->getChangedTime(), $options['query']['changed'], '?changed= timestamp should match the file entity changed time.');
  }

  /**
   * Tests thumbnail file and parent directory cleanup on media deletion.
   *
   * When a DAM media entity is deleted, acquia_dam_media_delete() removes
   * the associated managed files, thumbnails, and their parent directories
   * if empty.
   *
   * @covers ::acquia_dam_media_delete
   */
  public function testMediaDeleteThumbnailCleanup(): void {
    $media_type = $this->createPdfMediaType();
    $media = Media::create([
      'name' => 'Explorer owner\'s manual.pdf',
      'bundle' => $media_type->id(),
      'acquia_dam_asset_id' => [
        'asset_id' => '0324b0b2-5293-4aa0-b0aa-c85b003395e2',
        'version_id' => 'bd7a95eb-17a4-48b6-88cf-5443739e3683',
        'external_id' => '8a1ouvfchk',
      ],
    ]);
    $media->save();

    // Verify the thumbnail file was created.
    self::assertFalse($media->get('thumbnail')->isEmpty());
    $thumbnail_file = $media->get('thumbnail')->entity;
    self::assertInstanceOf(FileInterface::class, $thumbnail_file);
    $thumbnail_uri = $thumbnail_file->getFileUri();
    self::assertFileExists($thumbnail_uri);

    // Verify the thumbnail parent directory exists.
    $file_system = $this->container->get('file_system');
    $parent_dir = dirname($file_system->realpath($thumbnail_uri));
    self::assertDirectoryExists($parent_dir);
    $thumbnail_fid = $thumbnail_file->id();

    // Delete the media entity.
    $media->delete();

    // Assert the thumbnail file entity was deleted.
    $file_storage = $this->container->get('entity_type.manager')->getStorage('file');
    self::assertNull($file_storage->load($thumbnail_fid));

    // Assert the thumbnail file no longer exists on disk.
    self::assertFileDoesNotExist($thumbnail_uri);

    // Assert the parent directory was removed.
    self::assertDirectoryDoesNotExist($parent_dir);
  }

}
