<?php

declare(strict_types=1);

namespace Drupal\Tests\acquia_dam\Functional\Drush\Commands;

use Drupal\media\Entity\Media;
use Drupal\Tests\BrowserTestBase;
use Drush\TestTraits\DrushTestTrait;

/**
 * Tests for Acquia DAM Asset Update Drush Commands.
 *
 * Tests the refactored Drush commands that use AssetUpdater::syncAssets()
 * for efficient batch processing of DAM assets.
 *
 * @group acquia_dam
 */
class AcquiaDamAssetUpdateDrushCommandsTest extends BrowserTestBase {

  use DrushTestTrait;

  /**
   * {@inheritdoc}
   */
  protected bool $useOneTimeLoginLinks = FALSE;

  /**
   * {@inheritdoc}
   */
  protected $defaultTheme = 'stark';

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

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    // Create a Image media entity.
    $imageMedia = Media::create([
      'bundle' => 'acquia_dam_image_asset',
      'name' => 'Wheel Illustration.ai',
      'acquia_dam_asset_id' => [
        'asset_id' => '56ff14de-02cd-41b5-9a73-c917eab19abf',
        'version_id' => '7b67948f-ee7e-405c-a0cd-344a24d8afb2',
      ],
    ]);
    $imageMedia->save();

    // Create a PDF media entity.
    $pdfMedia = Media::create([
      'bundle' => 'acquia_dam_pdf_asset',
      'name' => 'bobsCarsAgileExercises.md',
      'acquia_dam_asset_id' => [
        'asset_id' => 'a56fb261-8ad5-4e0d-8323-0e8a3659ed38',
        'version_id' => '3f656c07-6a08-47b3-9403-16082d2fcda7',
      ],
    ]);
    $pdfMedia->save();
  }

  /**
   * Data provider for queue update assets command tests.
   *
   * With the refactored workflow, the command now uses syncAssets() which
   * reports statistics differently: dam_processed, queued_from_dam,
   * queued_from_version_check, unpublished.
   *
   * Note: The actual numbers depend on what the test fixtures return,
   * so we verify the output format/structure rather than exact counts.
   */
  public static function queueUpdateAssetsCommandProvider(): array {
    return [
      'default' => [
        'options' => [],
        'with_finalized_check' => FALSE,
      ],
      'batch_size' => [
        'options' => ['batch-size' => 1],
        'with_finalized_check' => FALSE,
      ],
      'media_type' => [
        'options' => ['media-type' => 'acquia_dam_image_asset'],
        'with_finalized_check' => FALSE,
      ],
      'date_filter_after' => [
        'options' => ['date-filter' => 'after 01/01/2020'],
        'with_finalized_check' => FALSE,
      ],
      'date_filter_before' => [
        'options' => ['date-filter' => 'before 12/31/2026'],
        'with_finalized_check' => FALSE,
      ],
      'date_filter_between' => [
        'options' => ['date-filter' => '01/01/2020 to 12/31/2026'],
        'with_finalized_check' => FALSE,
      ],
      'finalized_version' => [
        'options' => ['finalized-version' => TRUE],
        'with_finalized_check' => TRUE,
      ],
    ];
  }

  /**
   * Tests the acquia-dam:queue-update-assets command with various options.
   *
   * The refactored command now uses AssetUpdater::syncAssets() and outputs
   * different statistics: DAM API results and optional finalized version check.
   *
   * @dataProvider queueUpdateAssetsCommandProvider
   */
  public function testQueueUpdateAssetsCommandVariants(
    array $options,
    bool $with_finalized_check
  ): void {
    $this->drush('acquia-dam:queue-update-assets', [], $options);

    $output = $this->getOutput();

    // Assert new output format from refactored command.
    $this->assertStringContainsString("=== DAM API SYNC RESULTS ===", $output);
    $this->assertStringContainsString("Assets reported by DAM: 1", $output);
    $this->assertStringContainsString("Media items queued from DAM changes: 1", $output);

    if ($with_finalized_check) {
      $this->assertStringContainsString("=== FINALIZED VERSION CHECK ===", $output);
      $this->assertStringContainsString("Additional media queued from version check:", $output);
      $this->assertStringContainsString("TOTAL media items queued for update: 2", $output);
    } else {
      $this->assertStringContainsString("TOTAL media items queued for update: 1", $output);
      $this->assertStringNotContainsString("FINALIZED VERSION CHECK", $output);
    }

    $this->assertStringNotContainsString("error", strtolower($output));
  }

  /**
   * Tests the acquia-dam:process-update-queue drush command.
   */
  public function testProcessUpdateQueueCommand(): void {
    // First queue some items.
    $this->drush('acquia-dam:queue-update-assets');

    // Then process the queue.
    $this->drush('acquia-dam:process-update-queue');

    $output = $this->getOutput();
    $this->assertStringContainsString("=== QUEUE PROCESSING SUMMARY ===", $output);
    $this->assertStringContainsString("Items processed: 1", $output);
    $this->assertStringContainsString("Remaining items in queue: 0", $output);
  }

  /**
   * Tests the process queue command with limit option.
   */
  public function testProcessUpdateQueueWithLimit(): void {
    // First queue some items.
    $this->drush('acquia-dam:queue-update-assets');

    $output = $this->getOutput();
    // Extract the actual queued count from output.
    preg_match('/TOTAL media items queued for update: (\d+)/', $output, $matches);
    $queued_count = isset($matches[1]) ? (int) $matches[1] : 0;

    if ($queued_count > 0) {
      // Process with limit of 1.
      $this->drush('acquia-dam:process-update-queue', [], ['limit' => 1]);
      $this->assertProcessSummary(1, 1, 0);
    }
  }

  /**
   * Tests processing queue when it's empty.
   */
  public function testProcessEmptyQueue(): void {
    $this->drush('acquia-dam:process-update-queue');
    $this->assertProcessSummary(0, 0, 0);
  }

  /**
   * Tests the acquia-dam:update-assets combined command.
   *
   * The refactored command now shows DAM sync results followed by queue
   * processing results.
   */
  public function testUpdateAssetsCommand(): void {
    $this->drush('acquia-dam:update-assets');
    // Should contain messages from both queue and process operations.
    $this->assertUpdateSummary(1, 1, 1, 0);
  }

  /**
   * Tests the combined command with --date-filter option.
   */
  public function testUpdateAssetsWithDateFilterOption(): void {
    $this->drush('acquia-dam:update-assets', [], ['date-filter' => 'after 01/01/2020']);
    $this->assertUpdateSummary(1, 1, 1, 0);
  }

  /**
   * Tests the combined command with --finalized-version option.
   */
  public function testUpdateAssetsWithFinalizedVersionOption(): void {
    $this->drush('acquia-dam:update-assets', [], ['finalized-version' => TRUE]);
    $this->assertUpdateSummary(1, 2, 2, 0);
  }

  /**
   * Tests the combined command when no updates are needed.
   */
  public function testUpdateAssetsWithNoUpdatesNeeded(): void {
    // Switch to a fixture that returns no assets.
    \Drupal::state()->set('acquia_dam_test.last_edited_search_fixture', 'lastEditedSearchEmpty.json');

    $this->drush('acquia-dam:update-assets');
    $this->assertUpdateSummary(0, 0, 0, 0);
  }

  /**
   * Tests the combined command with all options.
   */
  public function testUpdateAssetsWithAllOptions(): void {
    $this->drush('acquia-dam:update-assets', [], [
      'date-filter' => '01/01/2020 to 12/31/2026',
      'batch-size' => 10,
      'limit' => 50,
      'media-type' => 'acquia_dam_image_asset',
      'finalized-version' => TRUE,
    ]);
    $this->assertUpdateSummary(1, 1, 1, 0);
  }

  /**
   * Asserts that the process summary output is correct.
   *
   * @param int $queued
   *   The expected number of items in the queue at start.
   * @param int $processed
   *   The expected number of processed items.
   * @param int $remaining
   *   The expected number of remaining items in the queue.
   */
  private function assertProcessSummary(int $queued, int $processed, int $remaining): void {
    $output = $this->getOutput();

    $this->assertStringContainsString("Processing DAM media update queue (items in queue: {$queued})", $output);
    $this->assertStringContainsString("=== QUEUE PROCESSING SUMMARY ===", $output);
    $this->assertStringContainsString("Items processed: {$processed}", $output);
    $this->assertStringContainsString("Remaining items in queue: {$remaining}", $output);
  }

  /**
   * Asserts that the update summary output is correct.
   *
   * @param int $reported
   *   The expected number of items reported in search query for update.
   * @param int $queued
   *   The expected number of items in the queue at start.
   * @param int $processed
   *   The expected number of processed items.
   * @param int $remaining
   *   The expected number of remaining items in the queue.
   */
  private function assertUpdateSummary(int $reported, int $queued, int $processed, int $remaining): void {
    $output = $this->getOutput();

    $this->assertStringContainsString("=== STARTING DAM ASSET UPDATE AND PROCESSING ===", $output);
    $this->assertStringContainsString("=== DAM API SYNC RESULTS ===", $output);
    $this->assertStringContainsString("Assets reported by DAM: {$reported}", $output);
    $this->assertStringContainsString("Media items queued from DAM changes: {$reported}", $output);
    $this->assertStringContainsString("Total queued for processing: {$queued}", $output);
    if ($queued > 0) {
      $this->assertStringContainsString("=== QUEUE PROCESSING SUMMARY ===", $output);
      $this->assertStringContainsString("Items processed: {$processed}", $output);
      $this->assertStringContainsString("Remaining items in queue: {$remaining}", $output);
      $this->assertStringContainsString("=== DAM ASSET UPDATE AND PROCESSING COMPLETED ===", $output);
    }
  }

  /**
   * Tests command aliases work correctly.
   */
  public function testCommandAliases(): void {
    // Test queue command alias.
    $this->drush('adqua');
    $this->assertStringContainsString("DAM API SYNC RESULTS", $this->getOutput());

    // Test process command alias.
    $this->drush('adpuq');
    $this->assertStringContainsString("QUEUE PROCESSING SUMMARY", $this->getOutput());

    // Test combined command alias.
    $this->drush('adua');
    $this->assertStringContainsString("STARTING DAM ASSET UPDATE", $this->getOutput());
    $this->assertStringContainsString("COMPLETED", $this->getOutput());

    // Test resolve media type command alias.
    $this->drush('adramt');
    $this->assertStringContainsString("Processing 2 media items in batches of 50", $this->getOutput());
    $this->assertStringContainsString("=== MEDIA TYPE RESOLUTION SUMMARY ===", $this->getOutput());
  }

  /**
   * Tests the acquia-dam:resolve-asset-media-type command.
   */
  public function testResolveAssetMediaTypeCommand(): void {
    $this->drush('acquia-dam:resolve-asset-media-type');

    $output = $this->getOutput();
    $this->assertStringContainsString("Processing 2 media items in batches of 50", $output);
    $this->assertStringContainsString("Processing chunk 1/1 (2 items)", $output);
    $this->assertStringContainsString("=== MEDIA TYPE RESOLUTION SUMMARY ===", $output);
    $this->assertStringContainsString("Media items processed: 2", $output);
    $this->assertStringContainsString("Media items updated: 0", $output);
  }

  /**
   * Tests resolve media type command with batch-size option.
   */
  public function testResolveAssetMediaTypeWithBatchSize(): void {
    $this->drush('acquia-dam:resolve-asset-media-type', [], ['batch-size' => 1]);

    $output = $this->getOutput();
    $this->assertStringContainsString("Processing 2 media items in batches of 1", $output);
    $this->assertStringContainsString("Processing chunk 1/2 (1 items)", $output);
    $this->assertStringContainsString("Processing chunk 2/2 (1 items)", $output);
  }

  /**
   * Tests resolve media type command with media-type filter.
   */
  public function testResolveAssetMediaTypeWithMediaTypeFilter(): void {
    // Create a Image media entity with asset of type video.
    $imageMedia = Media::create([
      'bundle' => 'acquia_dam_image_asset',
      'name' => 'Wheel Illustration.ai',
      'acquia_dam_asset_id' => [
        'asset_id' => 'efb03f75-3c42-497b-baa9-5ec79d1f56af',
        'version_id' => '77b3dd66-e590-4259-82db-25e7f4e62778',
      ],
    ]);
    $imageMedia->save();
    $mid = $imageMedia->id();
    $media_storage = \Drupal::entityTypeManager()->getStorage('media');
    $media = $media_storage->load($mid);
    $this->assertSame('acquia_dam_image_asset', $media->bundle());

    $this->drush('acquia-dam:resolve-asset-media-type', [], ['media-type' => 'acquia_dam_image_asset']);
    $output = $this->getOutput();

    $this->assertStringContainsString("Processing 2 media items in batches of 50", $output);
    $this->assertStringContainsString("Processing chunk 1/1 (2 items)", $output);
    $this->assertStringContainsString("Updated media 3 from acquia_dam_image_asset to acquia_dam_video_asset", $output);
    $this->assertStringContainsString("=== MEDIA TYPE RESOLUTION SUMMARY ===", $output);
    $this->assertStringContainsString("Media items processed: 2", $output);
    $this->assertStringContainsString("Media items updated: 1", $output);

    // Test needs to clear its local cache for the entity to see the changes.
    $media_storage->resetCache([$mid]);
    // Validate that media is now of type acquia_dam_video_asset.
    $media = $media_storage->load($mid);
    $this->assertSame('acquia_dam_video_asset', $media->bundle());

    // Visit the /admin/content/media page and validate the media items are actually updated to new media type.
    $this->drupalLogin($this->drupalCreateUser([
      'access media overview',
      'administer media',
    ]));
    $this->drupalGet('/admin/content/media');
    $this->assertSession()->statusCodeEquals(200);
    $firstRow = $this->assertSession()->elementExists('css', 'table tbody tr:first-child');
    $this->assertStringContainsString('SD-Social Promo_.mp4', $firstRow->find('css', 'td.views-field-name')->getText());
    $this->assertStringContainsString('Acquia DAM: Video', $firstRow->find('css', 'td.views-field-bundle')->getText());
  }

  /**
   * Tests resolve media type command with no media items.
   */
  public function testResolveAssetMediaTypeWithNoMediaItems(): void {
    // Delete all media items.
    $media_storage = \Drupal::entityTypeManager()->getStorage('media');
    $media_ids = $media_storage->getQuery()
      ->accessCheck(FALSE)
      ->execute();

    foreach ($media_ids as $media_id) {
      $media = $media_storage->load($media_id);
      $media->delete();
    }

    $this->drush('acquia-dam:resolve-asset-media-type');

    $output = $this->getOutput();
    $this->assertStringContainsString("No media items found to process", $output);
  }

  /**
   * Tests that unpublished assets are reported correctly.
   */
  public function testQueueAssetsWithUnpublishedAssets(): void {
    // Switch to a fixture that has unpublished assets.
    \Drupal::state()->set(
      'acquia_dam_test.last_edited_search_fixture',
      'lastEditedSearchAssetBecameUnreleased.json'
    );

    $this->drush('acquia-dam:queue-update-assets');

    $output = $this->getOutput();

    $this->assertStringContainsString("Media items unpublished:", $output);
  }

  /**
   * Tests that finalized version check processes remaining media.
   */
  public function testFinalizedVersionCheckProcessesRemainingMedia(): void {
    // Create additional media with an asset ID that exists in fixtures.
    // Use the PDF asset ID from fixture 4f656c07-6a08-47b3-9403-16082d2fcda2.
    $extraMedia = Media::create([
      'bundle' => 'acquia_dam_pdf_asset',
      'name' => 'Extra Media For Version Check',
      'acquia_dam_asset_id' => [
        'asset_id' => '4f656c07-6a08-47b3-9403-16082d2fcda2',
        'version_id' => 'old-version-id-needs-update',
      ],
    ]);
    $extraMedia->save();

    $this->drush('acquia-dam:queue-update-assets', [], ['finalized-version' => TRUE]);

    $output = $this->getOutput();

    // Should see finalized version check section.
    $this->assertStringContainsString("=== FINALIZED VERSION CHECK ===", $output);
    $this->assertStringContainsString("Additional media queued from version check:", $output);
  }

}
