<?php

namespace Drupal\Tests\image_replace\Functional;

use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\file\FileInterface;
use Drupal\image\Entity\ImageStyle;
use Drupal\node\Entity\Node;
use Drupal\user\UserInterface;

/**
 * Tests the administrative interface of the image replace module.
 *
 * @group image_replace
 */
class AdminTest extends ImageReplaceTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = ['views', 'node', 'field_ui', 'image_replace'];

  /**
   * The test style name.
   */
  protected string $styleName;

  /**
   * The test admin user.
   */
  protected UserInterface $adminUser;

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

    // Create an image style containing the replace effect.
    $this->styleName = 'image_replace_test';
    $this->createImageStyle($this->styleName);

    // Add the replacement image field to the article bundle.
    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
    $this->createImageField('image_replacement', 'article');

    // Add the original image field to the article bundle and specify
    // the replacement image as replacement.
    $this->createImageField('image_original', 'article');

    $entity_display_repository = $this->container->get(EntityDisplayRepositoryInterface::class);
    assert($entity_display_repository instanceof EntityDisplayRepositoryInterface);
    $display = $entity_display_repository->getViewDisplay('node', 'article', 'teaser');
    $display_options = $display->getComponent('image_original');
    $display_options['type'] = 'image';
    $display_options['settings']['image_style'] = $this->styleName;
    $display->set('status', TRUE);
    $display->setComponent('image_original', $display_options);
    $display->save();

    $display = $entity_display_repository->getViewDisplay('node', 'article', 'full');
    $display_options = $display->getComponent('image_original');
    $display_options['type'] = 'image';
    $display_options['settings']['image_style'] = NULL;
    $display->set('status', TRUE);
    $display->setComponent('image_original', $display_options);
    $display->save();

    $adminUser = $this->drupalCreateUser([
      'access content',
      'administer content types',
      'administer image styles',
      'administer node fields',
      'administer nodes',
      'create article content',
      'delete any article content',
      'edit any article content',
    ]);
    assert($adminUser !== FALSE);
    $this->adminUser = $adminUser;
  }

  /**
   * Tests image replacement on node entities.
   */
  public function testFieldEditUi(): void {
    [$original_file, $replacement_file] = $this->createTestFiles();
    $file_system = $this->container->get(FileSystemInterface::class);
    assert($file_system instanceof FileSystemInterface);

    // Create an unrelated image style.
    $unrelated_style_name = 'other_style';
    $style = ImageStyle::create([
      'name' => $unrelated_style_name,
      'label' => $this->randomString(),
    ]);
    $style->save();

    $this->drupalLogin($this->adminUser);

    $this->drupalGet('admin/structure/types/manage/article/fields/node.article.image_original');
    $this->assertSession()->statusCodeEquals(200);

    // Verify that a select field is present with a list of available source
    // fields for the generated image style.
    $field_name = 'third_party_settings[image_replace][image_style_map][' . $this->styleName . '][source_field]';
    $this->assertSession()->fieldExists($field_name);
    $items = $this->getSession()->getPage()->findField($field_name)?->findAll('xpath', 'option');
    assert($items !== NULL);
    $contains_image_original = FALSE;
    $contains_image_replacement = FALSE;
    foreach ($items as $item) {
      $contains_image_original = $contains_image_original || $item->getValue() == 'image_original';
      $contains_image_replacement = $contains_image_replacement || $item->getValue() == 'image_replacement';
    }

    $this->assertFalse($contains_image_original, 'Original image field is not in the list of options.');
    $this->assertTrue($contains_image_replacement, 'Replacement image field is in the list of options.');

    // Verify that no select field is present for an image style which does not
    // contain the replacement effect.
    $field_name = 'third_party_settings[image_replace][image_style_map][' . $unrelated_style_name . '][source_field]';
    $this->assertSession()->fieldNotExists($field_name);

    // Choose the replacement image field as the replacement source.
    $field_name = 'third_party_settings[image_replace][image_style_map][' . $this->styleName . '][source_field]';
    $edit = [
      $field_name => 'image_replacement',
    ];
    $this->submitForm($edit, 'Save settings');
    $this->assertSession()->statusCodeEquals(200);

    // Verify that no message is displayed if the mapping changes when there is
    // no existing content.
    $this->assertSession()->pageTextNotContains('The image replacement settings have been modified. As a result, it is necessary to rebuild the image replacement mapping for existing content. Note: The replacement mapping is updated automatically when saving an entity.');

    // Post new content.
    $edit = [
      'title[0][value]' => $this->randomString(),
      'promote[value]' => 1,
    ];
    $original_uri = $original_file->getFileUri();
    assert($original_uri !== NULL);
    $replacement_uri = $replacement_file->getFileUri();
    assert($replacement_uri !== NULL);
    $edit['files[image_original_0]'] = $file_system->realpath($original_uri);
    $edit['files[image_replacement_0]'] = $file_system->realpath($replacement_uri);
    $this->drupalGet('node/add/article');
    $this->submitForm($edit, 'Save');
    $this->assertSession()->statusCodeEquals(200);

    $edit = [
      'image_original[0][alt]' => $this->randomString(),
      'image_replacement[0][alt]' => $this->randomString(),
    ];
    $this->submitForm($edit, 'Save');
    $this->assertSession()->statusCodeEquals(200);

    preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
    assert(isset($matches[1]));
    $node = Node::load($matches[1]);
    assert($node !== NULL);

    $file_url_generator = $this->container->get(FileUrlGeneratorInterface::class);
    assert($file_url_generator instanceof FileUrlGeneratorInterface);

    // Verify that the original image is shown on the full node view.
    $original_entity = $node->image_original->entity;
    assert($original_entity instanceof FileInterface);
    $original_uri = $original_entity->getFileUri();
    assert($original_uri !== NULL);
    $generated_url = $file_url_generator->generateAbsoluteString($original_uri);
    $relative_url = $file_url_generator->transformRelative($generated_url);
    $this->assertSession()->responseContains(Html::escape($relative_url));

    $generated_image_data = $this->drupalGet($generated_url);
    $this->assertSession()->statusCodeEquals(200);

    $default_scheme = \Drupal::config('system.file')->get('default_scheme');
    $file_destination = $default_scheme . '://';
    // Assert that the result is the original image.
    $generated_uri = $file_system->saveData($generated_image_data, $file_destination);
    $this->assertTrue($this->imageIsOriginal($generated_uri), 'The generated file should be the same as the original file on full node view.');

    // Verify that the replacement image is shown on the teaser.
    $this->drupalGet('node');
    $this->assertSession()->statusCodeEquals(200);
    $generated_url = ImageStyle::load($this->styleName)?->buildUrl($original_uri);
    assert($generated_url !== NULL);
    $relative_url = $file_url_generator->transformRelative($generated_url);
    $this->assertSession()->responseContains(Html::escape($relative_url));

    $generated_image_data = $this->drupalGet($generated_url);
    $this->assertSession()->statusCodeEquals(200);

    // Assert that the result is the replacement image.
    $generated_uri = $file_system->saveData($generated_image_data, $file_destination);
    $this->assertTrue($this->imageIsReplacement($generated_uri), 'The generated file should be the same as the replacement file on teaser.');

    // Go back to the field settings and reset the replacement mapping.
    $field_name = 'third_party_settings[image_replace][image_style_map][' . $this->styleName . '][source_field]';
    $edit = [
      $field_name => '0',
    ];
    $this->drupalGet('admin/structure/types/manage/article/fields/node.article.image_original');
    $this->submitForm($edit, 'Save settings');
    $this->assertSession()->statusCodeEquals(200);

    // Verify that a message is displayed if the mapping changes when there is
    // existing content.
    $this->assertSession()->pageTextContains('The image replacement settings have been modified. As a result, it is necessary to rebuild the image replacement mapping for existing content. Note: The replacement mapping is updated automatically when saving an entity.');
  }

}
