<?php

namespace Drupal\Tests\acquia_dam\Kernel;

use Drupal\image\Entity\ImageStyle;

/**
 * Tests image style helper functions.
 *
 * @group acquia_dam_kernel
 */
class ImageStyleHelperTest extends AcquiaDamKernelTestBase {

  /**
   * Image style helper service.
   *
   * @var \Drupal\acquia_dam\ImageStyleHelper
   */
  protected $imageStyleHelper;

  /**
   * Cache backend service.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cache;

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

  /**
   * {@inheritdoc}
   */
  public function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('crop');

    $this->imageStyleHelper = $this->container->get('acquia_dam.image_style_support');
    $this->cache = $this->container->get('cache.default');
  }

  /**
   * Tests image width and height recalculation for large images.
   *
   * @param float $width
   *   Original width.
   * @param float $height
   *   Original height.
   * @param array $expected_values
   *   Calculated width and height.
   *
   * @testWith [200, 200, [200, 200]]
   *           [1000, 1000, [1000, 1000]]
   *           [1024, 4092, [512, 2048]]
   *           [4092, 4092, [2048, 2048]]
   *           [6000, 4092, [2048, 1396]]
   */
  public function testLargeImageHandling(float $width, float $height, array $expected_values) {
    $this->assertEquals($expected_values, $this->imageStyleHelper->handleLargeImages($width, $height));
  }

  /**
   * Test custom uri builder.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function testBuildUriForCrop() {
    $image_style = ImageStyle::create(['name' => 'test']);
    $image_style->save();
    $this->assertEquals(
      'acquia-dam://styles/test/acquia-dam/asset/version.png',
      $this->imageStyleHelper->buildUriForCrop('asset', 'version', 'test')
    );
  }

  /**
   * Test crop position calculation for DAM.
   *
   * @param int $x
   *   Relative X coordinate (%).
   * @param float $width
   *   Original image width.
   * @param int $y
   *   Relative Y coordinate (%).
   * @param float $height
   *   Original image height.
   * @param array $expected_position
   *   Expected calculated position on crop.
   *
   * @dataProvider dataProviderCropUpdate
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function testCropUpdate(int $x, float $width, int $y, float $height, array $expected_position) {
    $crop_values = [
      'type' => 'focal_point',
      'entity_type' => 'file',
      'uri' => 'valid_uri',
    ];

    /** @var \Drupal\crop\CropStorageInterface $storage */
    $storage = $this->entityTypeManager->getStorage('crop');
    /** @var \Drupal\crop\CropInterface $crop */
    $crop = $storage->create($crop_values);

    $crop = $this->imageStyleHelper->relativeToAbsolute($x, $y, $width, $height, $crop);
    $this->assertEquals($expected_position, $crop->position());
  }

  /**
   * Data provider for test.
   *
   * @return array
   *   Test data and expected result for test.
   */
  public static function dataProviderCropUpdate(): array {
    return [
      'simple_image_top_left_corner' => [
        1,
        1500,
        1,
        1500,
        ['x' => 15, 'y' => 15],
      ],
      'simple_image_inner_position' => [
        20,
        1500,
        30,
        1500,
        ['x' => 300, 'y' => 450],
      ],
      'large_width_inner_position' => [
        20,
        4000,
        30,
        1500,
        ['x' => 410, 'y' => 230],
      ],
      'large_height_inner_position' => [
        80,
        2000,
        30,
        4092,
        ['x' => 800, 'y' => 614],
      ],
      'large_widht_height_center_position' => [
        50,
        4092,
        50,
        4092,
        ['x' => 1024, 'y' => 1024],
      ],
      'large_width_height_right_left_position' => [
        99,
        4092,
        99,
        4092,
        ['x' => 2028, 'y' => 2028],
      ],
    ];
  }

  /**
   * Tests absolute position value transform to relative.
   *
   * @param int $x
   *   Absolute 'x' position.
   * @param int $y
   *   Absolute 'y' position.
   * @param float $width
   *   Image width.
   * @param float $height
   *   Image height.
   * @param array $expected_position
   *   Expected absolute position values.
   *
   * @dataProvider dataProviderAbsoluteToRelative
   */
  public function testAbsoluteToRelative(int $x, int $y, float $width, float $height, array $expected_position) {
    $relative_position = $this->imageStyleHelper->absoluteToRelative($x, $y, $width, $height);
    $this->assertEquals($expected_position, $relative_position);
  }

  /**
   * Data provider for test.
   *
   * @return array
   *   Test data and expected result for test.
   */
  public static function dataProviderAbsoluteToRelative(): array {
    return [
      [
        15,
        15,
        1500,
        1500,
        ['x' => 1, 'y' => 1],
      ],
      [
        1024,
        1024,
        4092,
        4092,
        ['x' => 50, 'y' => 50],
      ],
      [
        300,
        450,
        1500,
        1500,
        ['x' => 20, 'y' => 30],
      ],
    ];
  }

  /**
   * Tests caching for image style methods.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function testImageStyleCaching(): void {
    // Create test image styles.
    $this->createImageStyle('test_cache_style_1', 'Test Cache Style 1');
    $this->createImageStyle('test_cache_style_2', 'Test Cache Style 2');

    // Test getImageStylesBySupportStatus caching.
    $cache_id_support = 'acquia_dam:image_styles:support_status';
    $this->assertCacheEmpty($cache_id_support, 'Support status cache empty initially.');

    // First call - should populate cache with 2 styles.
    $this->imageStyleHelper->getImageStylesBySupportStatus();
    $this->assertCacheContainsCount($cache_id_support, ['supported'], 2, 'Cache contains 2 image styles initially.');

    // Add a third image style - cache should be cleared.
    $this->createImageStyle('test_cache_style_3', 'Test Cache Style 3');
    $this->assertCacheEmpty($cache_id_support, 'Support status cache invalidated after adding new image style.');

    $this->imageStyleHelper->getImageStylesBySupportStatus();
    $this->assertCacheContainsCount($cache_id_support, ['supported'], 3, 'Cache contains 3 image styles after addition.');

    // Test getAllowedImageStyles caching.
    $cache_id_allowed = 'acquia_dam:allowed_image_styles';
    $this->config('acquia_dam.settings')->set('allowed_image_styles', [])->save();
    $this->assertCacheEmpty($cache_id_allowed, 'Allowed styles cache empty initially.');

    // Set both as allowed styles.
    $this->config('acquia_dam.settings')
      ->set('allowed_image_styles', ['test_cache_style_1', 'test_cache_style_2'])
      ->save();
    $this->imageStyleHelper->getAllowedImageStyles();
    $this->assertCacheContainsCount($cache_id_allowed, [], 2, 'Cache contains 2 allowed image styles.');

    // Verify cache invalidation on config change.
    $this->config('acquia_dam.settings')->set('some_setting', 'value')->save();
    $this->assertCacheEmpty($cache_id_support, 'Cache invalidated on config change.');
  }

  /**
   * Helper method to create an image style.
   *
   * @param string $name
   *   Image style machine name.
   * @param string $label
   *   Image style label.
   */
  private function createImageStyle(string $name, string $label): void {
    $style = ImageStyle::create(['name' => $name, 'label' => $label]);
    $style->save();
  }

  /**
   * Helper method to assert cache is empty.
   *
   * @param string $cache_id
   *   Cache ID to check.
   * @param string $message
   *   Message on assertion failure.
   */
  private function assertCacheEmpty(string $cache_id, string $message): void {
    $this->assertFalse($this->cache->get($cache_id), $message);
  }

  /**
   * Helper method to assert cache contains specific count.
   *
   * @param string $cache_id
   *   Cache ID to check.
   * @param array $keys
   *   Keys to traverse in cached data.
   * @param int $count
   *   Expected count.
   * @param string $message
   *   Message on assertion failure.
   */
  private function assertCacheContainsCount(string $cache_id, array $keys, int $count, string $message): void {
    $cached_data = $this->cache->get($cache_id);
    $this->assertNotFalse($cached_data, 'Cache should be populated.');
    $data = $cached_data->data;
    foreach ($keys as $key) {
      $data = $data[$key];
    }
    $this->assertCount($count, $data, $message);
  }

  /**
   * Tests hook_image_style_delete removes style from config.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function testImageStyleDeleteHook(): void {
    // Create image styles.
    $style1 = ImageStyle::create(['name' => 'test_style_1', 'label' => 'Test Style 1']);
    $style1->save();
    $style2 = ImageStyle::create(['name' => 'test_style_2', 'label' => 'Test Style 2']);
    $style2->save();

    // Set both as allowed styles.
    $this->config('acquia_dam.settings')
      ->set('allowed_image_styles', ['test_style_1', 'test_style_2'])
      ->save();

    // Delete one style (triggers hook).
    $style1->delete();

    // Verify it's removed from config.
    $allowed = $this->config('acquia_dam.settings')->get('allowed_image_styles');
    $this->assertNotContains('test_style_1', $allowed, 'Deleted style removed from config.');
    $this->assertContains('test_style_2', $allowed, 'Other style remains in config.');
    $this->assertCount(1, $allowed, 'Only one style remains.');
  }

}
