<?php

namespace Drupal\Tests\acquia_dam\Kernel;

use Drupal\acquia_dam\EmbedCodeFactory;
use Drupal\image\Entity\ImageStyle;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media\MediaInterface;
use ReflectionClass;

/**
 * Tests embed code field formatter and dimension methods.
 *
 * @coversDefaultClass \Drupal\acquia_dam\EmbedCodeFactory
 *
 * @group acquia_dam_kernel
 */
class EmbedCodeFormatterTest extends AcquiaDamKernelTestBase {

  /**
   * Test media type.
   *
   * @var \Drupal\media\Entity\MediaType
   */
  protected MediaType $testMediaType;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'image',
    'file',
    'media',
    'media_library',
    'acquia_dam',
    'acquia_dam_test',
    'views',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    // Create a test media type.
    $this->testMediaType = $this->createPdfMediaType();
  }

  /**
   * Tests image embed code.
   *
   * @covers ::renderAsset
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function testImageEmbedCode(): void {
    $media_type = $this->createImageMediaType();
    $media = Media::create([
      'bundle' => $media_type->id(),
      'name' => 'Wheel Illustration.ai',
      'acquia_dam_asset_id' => [
        'asset_id' => '56ff14de-02cd-41b5-9a73-c917eab19abf',
      ],
    ]);
    $media->save();
    assert($media instanceof MediaInterface);

    $build = EmbedCodeFactory::renderAsset('original', $media);
    self::assertEquals([
      '#theme' => 'image',
      '#uri' => 'acquia-dam://56ff14de-02cd-41b5-9a73-c917eab19abf/4f656c07-6a08-47b3-9403-16082d2fcda5',
      '#alt' => 'Wheel Illustration',
      '#width' => 157,
      '#height' => 120,
    ], $build);
    $this->render($build);
    $this->assertStringContainsString(
      '<img src="https://laser.widen.net/content/kcnabdscl5/web/Wheel%20Illustration.ai?v=4f656c07-6a08-47b3-9403-16082d2fcda5" width="157" height="120" alt="Wheel Illustration" loading="lazy" />',
      $this->getRawContent()
    );
  }

  /**
   * Tests embed formatter.
   *
   * @dataProvider embedFormatterData
   * @covers ::renderAsset
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function testEmbedFormatter(string $embed_style, string $embed_key) {
    $embed_data = file_get_contents(__DIR__ . "/../../fixtures/0324b0b2-5293-4aa0-b0aa-c85b003395e2.json");
    $asset_data = json_decode($embed_data, TRUE);
    $media = Media::create([
      'bundle' => $this->testMediaType->id(),
      'name' => 'test',
      'acquia_dam_asset_id' => [
        'asset_id' => '0324b0b2-5293-4aa0-b0aa-c85b003395e2',
        'version_id' => '7b67948f-ee7e-405c-a0cd-344a24d8afb2',
        'external_id' => '8a1ouvfchk',
      ],
      'acquia_dam_embeds' => [
        'value' => $asset_data['embeds'],
      ],
    ]);
    $media->save();
    assert($media instanceof MediaInterface);

    $render_as_field = $this->renderAssetField($media, $embed_style);
    $renderer = \Drupal::service('renderer');

    $rendered_with_factory = EmbedCodeFactory::renderAsset(
      $embed_style,
      $media);
    $this->assertStringContainsString($renderer->renderRoot($rendered_with_factory), $render_as_field);
  }

  /**
   * Renders media field with view builder.
   *
   * @param \Drupal\media\Entity\Media $media
   *   Media entity instance.
   * @param string $embed_style
   *   Field formatter config value.
   *
   * @return callable|\Drupal\Component\Render\MarkupInterface|mixed
   *   Rendered field markup.
   *
   * @throws \Exception
   */
  protected function renderAssetField(Media $media, string $embed_style) {
    $view_builder = \Drupal::entityTypeManager()->getViewBuilder('media');
    $render_array = $view_builder->viewField(
      $media->get('acquia_dam_asset_id'),
      [
        'settings' => [
          'embed_style' => $embed_style,
        ],
      ]
    );

    return $this->render($render_array);
  }

  /**
   * Data provider for testEmbedFormatter.
   *
   * @return \string[][]
   *   Data sets for testEmbedFormatter.
   */
  public static function embedFormatterData(): array {
    return [
      [
        'original',
        'original',
      ],
      [
        'inline_view_download',
        'document_viewer_with_download',
      ],
      [
        'inline_view',
        'document_viewer',
      ],
      [
        'link_text_download',
        'document_viewer_with_download',
      ],
      [
        'link_text',
        'document_viewer',
      ],
      [
        'link_thumbnail_download',
        'document_viewer_with_download',
      ],
      [
        'link_thumbnail',
        'document_viewer',
      ],
    ];
  }

  /**
   * Gets a private/protected method for testing.
   *
   * @param string $methodName
   *   The method name to get.
   *
   * @return \ReflectionMethod
   *   The reflection method.
   */
  private function getPrivateMethod(string $methodName): \ReflectionMethod {
    $reflection = new ReflectionClass(EmbedCodeFactory::class);
    $method = $reflection->getMethod($methodName);
    $method->setAccessible(TRUE);
    return $method;
  }

  /**
   * Data provider for getImageDimensions tests.
   *
   * @return array
   *   Test cases with image properties, format, expected result, and description.
   */
  public static function getImageDimensionsDataProvider(): array {
    return [
      'valid_properties' => [
        ['width' => 800, 'height' => 600],
        'thumbnail',
        ['width' => 800, 'height' => 600],
        'Should return original dimensions when valid',
        FALSE, // No image style needed
      ],
      'missing_width' => [
        ['height' => 600],
        'thumbnail',
        ['width' => 150, 'height' => 150],
        'Should fallback to image style when width missing',
        ['thumbnail' => ['width' => 150, 'height' => 150]],
      ],
      'zero_dimensions' => [
        ['width' => 0, 'height' => 0],
        'large',
        ['width' => 480, 'height' => 480],
        'Should fallback to image style when dimensions are zero',
        ['large' => ['width' => 480, 'height' => 480]],
      ],
      'partial_valid_dimensions' => [
        ['width' => 800, 'height' => 0],
        'medium',
        ['width' => 220, 'height' => 220],
        'Should fallback to image style when height is zero',
        ['medium' => ['width' => 220, 'height' => 220]],
      ],
      'original_format_zero_dimensions' => [
        ['width' => 0, 'height' => 0],
        'original',
        ['width' => 0, 'height' => 0],
        'Should return original dimensions even if zero when format is original',
        FALSE,
      ],
      'non_existent_image_style' => [
        ['width' => 0, 'height' => 0],
        'non_existent_style',
        ['width' => 0, 'height' => 0],
        'Should return original dimensions when image style does not exist',
        FALSE,
      ],
      'empty_properties' => [
        [],
        'thumbnail',
        ['width' => 100, 'height' => 100],
        'Should fallback to image style when properties are empty',
        ['thumbnail' => ['width' => 100, 'height' => 100]],
      ],
    ];
  }

  /**
   * Test getImageDimensions with various scenarios.
   *
   * @dataProvider getImageDimensionsDataProvider
   * @covers ::getImageDimensions
   */
  public function testGetImageDimensions(
    array $image_properties,
    string $format,
    array $expected,
    string $description,
    $image_styles
  ): void {
    // Create required image styles
    if ($image_styles) {
      foreach ($image_styles as $style_name => $dimensions) {
        $this->createTestImageStyle($style_name, $dimensions['width'], $dimensions['height']);
      }
    }

    $method = $this->getPrivateMethod('getImageDimensions');
    $result = $method->invokeArgs(null, [$format, $image_properties]);

    $this->assertEquals($expected, $result, $description);
  }

  /**
   * Data provider for getImageStyleDimensions tests.
   *
   * @return array
   *   Test cases with image style configuration and expected results.
   */
  public static function getImageStyleDimensionsDataProvider(): array {
    return [
      'scale_effect' => [
        'test_scale',
        [
          ['id' => 'image_scale', 'data' => ['width' => 300, 'height' => 200, 'upscale' => TRUE]],
        ],
        ['width' => 300, 'height' => 200],
        'Should extract dimensions from scale effect',
      ],
      'crop_effect' => [
        'test_crop',
        [
          ['id' => 'image_crop', 'data' => ['width' => 400, 'height' => 300, 'anchor' => 'center-center']],
        ],
        ['width' => 400, 'height' => 300],
        'Should extract dimensions from crop effect',
      ],
      'resize_effect' => [
        'test_resize',
        [
          ['id' => 'image_resize', 'data' => ['width' => 250, 'height' => 180]],
        ],
        ['width' => 250, 'height' => 180],
        'Should extract dimensions from resize effect',
      ],
      'multiple_effects_first_wins' => [
        'test_multiple',
        [
          ['id' => 'image_scale', 'data' => ['width' => 500, 'height' => 400, 'upscale' => TRUE]],
          ['id' => 'image_crop', 'data' => ['width' => 200, 'height' => 150, 'anchor' => 'center-center']],
        ],
        ['width' => 500, 'height' => 400],
        'Should use dimensions from first effect when multiple effects exist',
      ],
      'only_width_specified' => [
        'test_width_only',
        [
          ['id' => 'image_scale', 'data' => ['width' => 350, 'upscale' => FALSE]],
        ],
        ['width' => 350, 'height' => 0],
        'Should handle effect with only width specified',
      ],
      'only_height_specified' => [
        'test_height_only',
        [
          ['id' => 'image_scale', 'data' => ['height' => 280, 'upscale' => FALSE]],
        ],
        ['width' => 0, 'height' => 280],
        'Should handle effect with only height specified',
      ],
      'no_dimensional_effects' => [
        'test_no_dimensions',
        [
          ['id' => 'image_desaturate', 'data' => []],
        ],
        ['width' => 0, 'height' => 0],
        'Should return zero dimensions when no dimensional effects exist',
      ],
      'empty_image_style' => [
        'test_empty',
        [],
        ['width' => 0, 'height' => 0],
        'Should return zero dimensions for empty image style',
      ],
      'partial_dimensions_multiple_effects' => [
        'test_partial',
        [
          ['id' => 'image_scale', 'data' => ['width' => 600]],
          ['id' => 'image_crop', 'data' => ['height' => 450, 'anchor' => 'center-center']],
        ],
        ['width' => 600, 'height' => 450],
        'Should combine dimensions from different effects when partially specified',
      ],
    ];
  }

  /**
   * Test getImageStyleDimensions with various image style configurations.
   *
   * @dataProvider getImageStyleDimensionsDataProvider
   * @covers ::getImageStyleDimensions
   */
  public function testGetImageStyleDimensions(
    string $style_name,
    array $effects,
    array $expected,
    string $description
  ): void {
    $image_style = ImageStyle::create([
      'name' => $style_name,
      'label' => ucfirst(str_replace('_', ' ', $style_name)),
    ]);

    // Add all effects to the image style
    foreach ($effects as $effect) {
      $image_style->addImageEffect($effect);
    }

    $image_style->save();

    $method = $this->getPrivateMethod('getImageStyleDimensions');
    $result = $method->invokeArgs(null, [$image_style]);

    $this->assertEquals($expected, $result, $description);
  }

  /**
   * Data provider for integration tests.
   *
   * @return array
   *   Integration test scenarios.
   */
  public static function integrationTestDataProvider(): array {
    return [
      'svg_fallback' => [
        [],
        'svg_thumbnail',
        ['width' => 120, 'height' => 120],
        'SVG files without metadata should fallback to image style',
        ['svg_thumbnail' => ['width' => 120, 'height' => 120]],
      ],
      'full_chain_with_style' => [
        ['width' => 0, 'height' => 0],
        'chain_test',
        ['width' => 333, 'height' => 222],
        'Should use image style when properties are invalid',
        ['chain_test' => ['width' => 333, 'height' => 222]],
      ],
      'full_chain_no_style' => [
        ['width' => 0, 'height' => 0],
        'non_existent_chain_test',
        ['width' => 0, 'height' => 0],
        'Should return original dimensions when both properties and style are invalid',
        FALSE,
      ],
    ];
  }

  /**
   * Integration tests for complete dimension resolution chain.
   *
   * @dataProvider integrationTestDataProvider
   * @covers ::getImageDimensions
   * @covers ::getImageStyleDimensions
   */
  public function testGetImageDimensionsIntegration(
    array $image_properties,
    string $format,
    array $expected,
    string $description,
    $image_styles
  ): void {
    // Create required image styles
    if ($image_styles) {
      foreach ($image_styles as $style_name => $dimensions) {
        $this->createTestImageStyle($style_name, $dimensions['width'], $dimensions['height']);
      }
    }

    $method = $this->getPrivateMethod('getImageDimensions');
    $result = $method->invokeArgs(null, [$format, $image_properties]);

    $this->assertEquals($expected, $result, $description);
  }

  /**
   * Creates a test image style with scale effect.
   *
   * @param string $name
   *   The image style name.
   * @param int $width
   *   The width.
   * @param int $height
   *   The height.
   *
   * @return \Drupal\image\Entity\ImageStyle
   *   The created image style.
   */
  private function createTestImageStyle(string $name, int $width, int $height): ImageStyle {
    $image_style = ImageStyle::create([
      'name' => $name,
      'label' => ucfirst($name),
    ]);

    $image_style->addImageEffect([
      'id' => 'image_scale',
      'data' => [
        'width' => $width,
        'height' => $height,
        'upscale' => TRUE,
      ],
    ]);

    $image_style->save();
    return $image_style;
  }

}
