<?php

declare(strict_types=1);

namespace Drupal\Tests\acquiadam_asset_import\Kernel\Services;

use Drupal\acquia_dam\Entity\MediaSourceField;
use Drupal\Tests\acquia_dam\Kernel\AcquiaDamKernelTestBase;

/**
 * Tests the AssetQueueService functionality.
 *
 * @coversDefaultClass \Drupal\acquiadam_asset_import\Services\AssetQueueService
 *
 * @group acquiadam_asset_import
 */
final class AssetQueueServiceTest extends AcquiaDamKernelTestBase {

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

  /**
   * The asset import queue.
   *
   * @var \Drupal\acquiadam_asset_import\Services\AssetQueueService
   */
  protected $assetImportQueue;

  /**
   * The encoded category name.
   *
   * @var string
   */
  protected $encodedCategoryName;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->assetImportQueue = $this->container->get('acquiadam_asset_import.asset_queue');
    $this->encodedCategoryName = base64_encode('Testing');
  }

  /**
   * Tests the addAssetsToQueue method when no categories are configured.
   *
   * @covers ::addAssetsToQueue
   */
  public function testAddAssetsToQueueNoCategories(): void {
    $result = $this->assetImportQueue->addAssetsToQueue();
    $this->assertIsArray($result, 'Expected array result.');
    $this->assertEquals(
      [],
      $result,
    );
  }

  /**
   * Tests the addAssetsToQueue method when no items are added to the queue.
   *
   * @covers ::addAssetsToQueue
   */
  public function testNoItemsToAddToQueue(): void {

    // Add the category to the assert import configuration.
    $this->config('acquiadam_asset_import.settings')
      ->set('categories', [$this->encodedCategoryName => []])
      ->save();

    $result = $this->assetImportQueue->addAssetsToQueue();
    $this->assertIsArray($result, 'Expected array result.');

    $this->assertEquals(
      [
        "categories" => [
          "Testing" => [
            "queued" => 0,
            "total" => 7,
          ],
        ],
      ],
      $result,
    );

    // Create an Acquia DAM audio media type.
    $this->createAudioMediaType();

    $result = $this->assetImportQueue->addAssetsToQueue();
    $this->assertIsArray($result, 'Expected array result.');
    $this->assertEquals(
      [
        "categories" => [
          "Testing" => [
            "queued" => 1,
            "total" => 7,
          ],
        ],
      ],
      $result,
    );

  }

  /**
   * Tests the add assets to queue functionality.
   *
   * @covers ::addAssetsToQueue
   */
  public function testAddAssetsToQueue(): void {

    // Create an Acquia DAM Audio media type.
    $audio_media_type = $this->createAudioMediaType();
    // Assert media type exist.
    $this->assertNotNull(
      $audio_media_type,
      'Acquia DAM Audio media type should exist.'
    );
    // Add the categories to the asset import configuration,
    // but limit to a specific bundle.
    $this->config('acquiadam_asset_import.settings')
      ->set('categories', [
        $this->encodedCategoryName => [
          $audio_media_type->id() => $audio_media_type->id(),
        ],
      ])
      ->save();

    // Create an Acquia DAM video media type.
    $this->createVideoMediaType();

    $result = $this->assetImportQueue->addAssetsToQueue();
    $this->assertIsArray($result, 'Expected array result.');
    $this->assertEquals(
      [
        "categories" => [
          "Testing" => [
            "queued" => 0,
            "total" => 0,
          ],
        ],
      ],
      $result,
    );

    // Update the configuration but remove the bundle filter.
    $this->config('acquiadam_asset_import.settings')
      ->set('categories', [
        $this->encodedCategoryName => [],
      ])
      ->save();

    $result = $this->assetImportQueue->addAssetsToQueue();
    $this->assertIsArray($result, 'Expected array result.');
    $this->assertEquals(
      [
        "categories" => [
          "Testing" => [
            "queued" => 2,
            "total" => 7,
          ],
        ],
      ],
      $result,
    );

    $result = $this->assetImportQueue->addAssetsToQueue();
    $this->assertIsArray($result, 'Expected array result.');
    $this->assertEquals(
      [
        "categories" => [
          "Testing" => [
            "queued" => 0,
            "total" => 7,
          ],
        ],
      ],
      $result,
    );

  }

  /**
   * Tests the add assets to queue on cron during functionality.
   *
   * @covers ::addAssetsToQueue
   */
  public function testProcessItemsOnCron(): void {
    // Create an Acquia DAM video media type.
    $this->createVideoMediaType();

    // Add the category to the assert import configuration.
    $this->config('acquiadam_asset_import.settings')
      ->set('categories', [
        $this->encodedCategoryName => [],
      ])
      ->save();

    // Run cron to add and import item.
    $this->container->get('cron')->run();

    $medias = $this->entityTypeManager->getStorage('media')->loadMultiple();
    $media = reset($medias);
    $this->assertEquals(
      "efb03f75-3c42-497b-baa9-5ec79d1f56af, 4f656c07-6a08-47b3-9403-16082d2fcda3, mnmc58hipn",
      $media->get(MediaSourceField::SOURCE_FIELD_NAME)->getString(),
    );

  }

  /**
   * Tests the getFilters method.
   *
   * @covers ::getFilters
   */
  public function testGetFilters(): void {
    // Create media types with different asset search configurations
    $video_media_type = $this->createVideoMediaType();
    $audio_media_type = $this->createAudioMediaType();

    // Test with single media type
    $filter = $this->assetImportQueue->getFilters([
      $video_media_type->id() => $video_media_type->id()
    ]);

    $this->assertEquals(
      'ft:(video)',
      $filter,
      'Expected correct filter string for video media type'
    );

    // Test with multiple media types
    $filter = $this->assetImportQueue->getFilters([
      $video_media_type->id() => $video_media_type->id(),
      $audio_media_type->id() => $audio_media_type->id(),
    ]);
    $this->assertEquals(
      'ft:(video or audio)',
      $filter,
      'Expected correct combined filter string for video and audio media types'
    );

    // Test with non-existent media type
    $filter = $this->assetImportQueue->getFilters(['non_existent_type']);
    $this->assertEquals(
      '',
      $filter,
      'Expected empty string for non-existent media type'
    );
  }

  /**
   * Data provider for buildQuery test.
   *
   * @return array
   *   Test cases.
   */
  public static function buildQueryDataProvider(): array {
    return [
      'category without bundle filter' => [
        'source' => 'categories',
        'id' => 'Testing',
        'bundles' => [],
        'expected' => 'cat:({Testing})',
      ],
      'category with single bundle filter' => [
        'source' => 'categories',
        'id' => 'Testing',
        'bundles' => 'single_bundle',
        'expected' => 'cat:({Testing}) ft:(video)',
      ],
      'category with multiple bundle filters' => [
        'source' => 'categories',
        'id' => 'Testing',
        'bundles' => 'multiple_bundles',
        'expected' => 'cat:({Testing}) ft:(video or audio)',
      ],
      'asset group source' => [
        'source' => 'asset_groups',
        'id' => 'eae2014b-4264-486d-b59b-5ed126cb34c2',
        'bundles' => [],
        'expected' => 'assetgroup:({WidenQA})',
      ],
      'invalid source type' => [
        'source' => 'invalid_source',
        'id' => 'Testing',
        'bundles' => [],
        'expected' => null,
      ],
    ];
  }

  /**
   * Tests the buildQuery method.
   *
   * @covers ::buildQuery
   * @dataProvider buildQueryDataProvider
   */
  public function testBuildQuery(string $source, string $id, $bundles, $expected): void {
    // Use reflection to access the private buildQuery method
    $reflection = new \ReflectionClass($this->assetImportQueue);
    $method = $reflection->getMethod('buildQuery');
    $method->setAccessible(true);

    // Create media types with search configurations
    $video_media_type = $this->createVideoMediaType();
    $audio_media_type = $this->createAudioMediaType();

    // Prepare bundle data based on the test case
    $bundleData = [];
    if ($bundles === 'single_bundle') {
      $bundleData = [$video_media_type->id() => $video_media_type->id()];
    }
    elseif ($bundles === 'multiple_bundles') {
      $bundleData = [
        $video_media_type->id() => $video_media_type->id(),
        $audio_media_type->id() => $audio_media_type->id(),
      ];
    }

    $id = match($source) {
      'categories' => rawurldecode($id),
      'asset_groups' => $this->container->get('acquia_dam.client.factory')->getSiteClient()->convertAssetGroupUuidToName($id),
      default => $id,
    };

    // Execute the method and verify results
    $query = $method->invoke(
      $this->assetImportQueue,
      $source,
      $id,
      $bundleData
    );

    $this->assertSame(
      $expected,
      $query,
      "Expected $query for $source source type"
    );
  }

  /**
   * Tests that partial category term matches don't import unintended assets.
   *
   * The curly braces in the DAM API query ensure exact category matching,
   * preventing "test" from matching assets tagged with "Testing".
   *
   * @covers ::buildQuery
   * @covers ::addAssetsToQueue
   */
  public function testAssetImportOnExactCategory(): void {
    // Create a video media type for this test.
    $this->createVideoMediaType();

    // Use reflection to access the private buildQuery method.
    $reflection = new \ReflectionClass($this->assetImportQueue);
    $method = $reflection->getMethod('buildQuery');
    $method->setAccessible(TRUE);

    // Test that "test" category creates an exact match query.
    $query = $method->invoke(
      $this->assetImportQueue,
      'categories',
      'test',
      []
    );

    $this->assertSame(
      'cat:({test})',
      $query,
      'Expected query to use exact matching with curly braces for "test" category.'
    );

    // Test that "Testing" category also creates an exact match query.
    $query = $method->invoke(
      $this->assetImportQueue,
      'categories',
      'Testing',
      []
    );

    $this->assertSame(
      'cat:({Testing})',
      $query,
      'Expected query to use exact matching with curly braces for "Testing" category.'
    );

    // Verify that queries use curly braces for exact matching.
    // Without curly braces (cat:(test)), partial matching would occur.
    $this->assertStringContainsString(
      '{',
      $query,
      'Query should contain curly braces to enforce exact matching.'
    );


    // Test actual import behavior: Configure with "Testing" category.
    $this->config('acquiadam_asset_import.settings')
      ->set('categories', [
        $this->encodedCategoryName => [],
      ])
      ->save();

    // Import assets from "Testing" category.
    $result = $this->assetImportQueue->addAssetsToQueue();
    $this->assertIsArray($result, 'Expected array result.');
    $this->assertEquals(
      [
        "categories" => [
          "Testing" => [
            "queued" => 1,
            "total" => 7,
          ],
        ],
      ],
      $result,
    );

    // Verify the queue contains exactly one asset.
    $queue = $this->container->get('queue')->get('acquia_dam_asset_import');
    $this->assertEquals(1, $queue->numberOfItems(), 'Expected exactly 1 item in queue.');

    // Verify the asset UUID from "Testing" category.
    $item = $queue->claimItem();
    $this->assertNotNull($item, 'Queue item should exist.');
    $this->assertEquals(
      'efb03f75-3c42-497b-baa9-5ec79d1f56af',
      $item->data['asset_uuid'],
      'Expected specific asset from "Testing" category.'
    );

    // Test that duplicate imports are prevented.
    $result = $this->assetImportQueue->addAssetsToQueue();
    $this->assertIsArray($result, 'Expected array result.');
    $this->assertEquals(
      [
        "categories" => [
          "Testing" => [
            "queued" => 0,
            "total" => 7,
          ],
        ],
      ],
      $result,
    );
  }

}
