<?php

namespace Drupal\Tests\acquia_contenthub\Kernel\EventSubscriber\CleanUpStubs;

use Drupal\acquia_contenthub\AcquiaContentHubEvents;
use Drupal\acquia_contenthub\Event\CleanUpStubsEvent;
use Drupal\block_content\BlockContentInterface;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\depcalc\DependencyStack;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\acquia_contenthub\Kernel\Traits\FieldTrait;

/**
 * Test that LB inline block is handled correctly in CLEANUP_STUBS event.
 *
 * @group acquia_contenthub
 * @coversDefaultClass \Drupal\acquia_contenthub\EventSubscriber\CleanupStubs\LayoutBuilderInlineBlockStubCleanup
 *
 * @requires module depcalc
 *
 * @package Drupal\Tests\acquia_contenthub\Kernel\EventSubscriber\CleanUpStubs
 */
class LayoutBuilderInlineBlockStubCleanupTest extends KernelTestBase {

  use FieldTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'acquia_contenthub',
    'depcalc',
    'block',
    'block_content',
    'layout_builder',
    'user',
    'field',
    'text',
    'filter',
  ];

  /**
   * Event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcher
   */
  protected $dispatcher;

  /**
   * {@inheritdoc}
   *
   * @throws \Exception
   */
  protected function setup(): void {
    parent::setUp();

    $this->installEntitySchema('block_content');
    $this->installEntitySchema('field_config');
    $this->installSchema('layout_builder', 'inline_block_usage');

    $this->dispatcher = $this->container->get('event_dispatcher');
  }

  /**
   * Tests LayoutBuilderInlineBlockStubCleanup event subscriber.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function testLayoutBuilderInlineBlockStubCleanup() {
    $block_content = $this->createEntity();
    // Create again to have a duplicate.
    $this->createEntity();

    $event = new CleanUpStubsEvent($block_content, new DependencyStack());
    $this->dispatcher->dispatch($event, AcquiaContentHubEvents::CLEANUP_STUBS);

    // Inline block usage is not set, should not stop propagation.
    $this->assertFalse($event->isPropagationStopped());

    // Just insert a row in the table with fixed values.
    $this->addInlineBlockUsage($block_content->id());

    $event = new CleanUpStubsEvent($block_content, new DependencyStack());
    $this->dispatcher->dispatch($event, AcquiaContentHubEvents::CLEANUP_STUBS);

    // Inline block usage is there so propagation should be stopped.
    $this->assertTrue($event->isPropagationStopped());

    $blocks = \Drupal::entityTypeManager()
      ->getStorage('block_content')
      ->loadByProperties([
        'info' => $block_content->get('info')->value,
      ]);

    $this->assertEquals(1, count($blocks), 'Only 1 block should be after subscriber run');
  }

  /**
   * Tests LayoutBuilderInlineBlockStubCleanup event subscriber.
   *
   * Test blocks with same title but different body.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function testLayoutBuilderInlineBlockStubCleanupSameTitleDifferentBody() {
    $block_content1 = $this->createEntity();
    // Create again to have a duplicate.
    $block_content2 = $this->createEntity();
    $block_content2->set('body', 'Different body.')->save();

    $event = new CleanUpStubsEvent($block_content1, new DependencyStack());
    $this->dispatcher->dispatch($event, AcquiaContentHubEvents::CLEANUP_STUBS);

    // Inline block usage is not set, should not stop propagation.
    $this->assertFalse($event->isPropagationStopped());

    // Just insert a row in the table with fixed values.
    $this->addInlineBlockUsage($block_content1->id());

    $event = new CleanUpStubsEvent($block_content1, new DependencyStack());
    $this->dispatcher->dispatch($event, AcquiaContentHubEvents::CLEANUP_STUBS);

    // Inline block usage is there so propagation should be stopped.
    $this->assertTrue($event->isPropagationStopped());

    $blocks = \Drupal::entityTypeManager()
      ->getStorage('block_content')
      ->loadByProperties([
        'info' => $block_content1->get('info')->value,
      ]);

    $this->assertEquals(2, count($blocks), 'Both the blocks should be available after subscriber run');
  }

  /**
   * Tests cleanup with empty body field values.
   *
   * This test covers the bug where blocks with empty body fields were not
   * being matched as duplicates because the empty body value was being added
   * to the properties array, causing the loadByProperties query to fail.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  public function testLayoutBuilderInlineBlockStubCleanupWithEmptyBody() {
    $block_content1 = $this->createEntityWithEmptyBody();
    $block_content2 = $this->createEntityWithEmptyBody();
    $block_content2->set('info', $block_content1->get('info')->value);
    $block_content2->set('changed', $block_content1->get('changed')->value);
    $block_content2->save();

    $this->addInlineBlockUsage($block_content1->id());

    $event = new CleanUpStubsEvent($block_content1, new DependencyStack());
    $this->dispatcher->dispatch($event, AcquiaContentHubEvents::CLEANUP_STUBS);
    $this->assertTrue($event->isPropagationStopped());

    $blocks = \Drupal::entityTypeManager()
      ->getStorage('block_content')
      ->loadByProperties([
        'info' => $block_content1->get('info')->value,
      ]);

    $this->assertEquals(1, count($blocks), 'Only 1 block should remain after cleanup.');
    $remaining_block = reset($blocks);
    $this->assertNotEquals($block_content2->id(), $remaining_block->id(), 'The duplicate block_content2 should have been deleted');
    $this->assertEquals($block_content1->id(), $remaining_block->id(), 'The remaining block should be block_content1, not block_content2');
  }

  /**
   * Add a row into table with the given entity id.
   *
   * @param int $block_id
   *   Block content id.
   *
   * @throws \Exception
   */
  protected function addInlineBlockUsage($block_id) {
    \Drupal::database()->insert('inline_block_usage')
      ->fields([
        'block_content_id' => $block_id,
        'layout_entity_type' => 'node',
        'layout_entity_id' => 1,
      ])
      ->execute();
  }

  /**
   * Create a basic type block entity.
   *
   * @param string $info
   *   The block info/title.
   * @param string $body_value
   *   The body field value.
   *
   * @return \Drupal\block_content\BlockContentInterface
   *   Returns the newly created block content.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function createEntity(string $info = 'Test', string $body_value = 'Test.'): BlockContentInterface {
    $this->ensureBlockContentTypeExists();

    $block_content = BlockContent::create([
      'info' => $info,
      'type' => 'basic',
      'body' => [
        'value' => $body_value,
        'format' => 'plain_text',
      ],
    ]);
    $block_content->save();

    return $block_content;
  }

  /**
   * Create a basic type block entity with empty body field.
   *
   * @return \Drupal\block_content\BlockContentInterface
   *   Returns the newly created block content with empty body.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function createEntityWithEmptyBody(): BlockContentInterface {
    return $this->createEntity('Test Empty Body', '');
  }

  /**
   * Ensures the 'basic' block content type exists.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function ensureBlockContentTypeExists(): void {
    if (!BlockContentType::load('basic')) {
      $block_content_type = BlockContentType::create([
        'id' => 'basic',
        'label' => 'basic',
        'revision' => TRUE,
      ]);
      $block_content_type->save();

      $field_storage = $this->createFieldStorage(
        'body',
        'block_content',
        'text_with_summary',
      );
      $this->createFieldConfig(
        $field_storage,
        'basic'
      );

      block_content_add_body_field($block_content_type->id());
    }
  }

}
