<?php

namespace Drupal\Tests\pagedesigner\Kernel\PatternTests;

use Drupal\Tests\pagedesigner\Kernel\AbstractPagedesignerTestBase;
use Drupal\ui_patterns\UiPatterns;

/**
 * Base class for Kernel pattern tests.
 */
abstract class PatternTestBase extends AbstractPagedesignerTestBase {

  /**
   * The element handler.
   *
   * @var \Drupal\pagedesigner\Service\ElementHandler
   */
  protected $elementHandler = NULL;

  /**
   * The pattern ids to test.
   *
   * @var array
   */
  protected $patternIds = [];

  /**
   * The pattern definitions.
   *
   * @var array
   */
  protected $definitions = [];

  /**
   * The active definition.
   *
   * @var [type]
   */
  protected $activeDefinition = NULL;

  /**
   * The data to pass to the generation function.
   *
   * @var array
   */
  protected $patchData = [
    'parent' => 9999,
    'container' => 9999,
    'entity' => 9999,
  ];

  /**
   * The field data to be returned by the serialization function.
   *
   * @var array
   */
  protected $returnData = [];

  /**
   * {@inheritdoc}
   */
  public function setUp(): void {
    self::$modules[] = 'ui_patterns_library';
    self::$modules[] = 'pagedesigner_pattern_test';
    parent::setUp();
  }

  /**
   * {@inheritdoc}
   */
  public function assertPreConditions(): void {
    parent::assertPreConditions();
    $this->elementHandler = \Drupal::service('pagedesigner.service.element_handler');
    $manager = UiPatterns::getManager();
    $this->definitions = $manager->getDefinitions();
  }

  /**
   * Test the definitions.
   */
  public function testDefinition() {
    $this->assertTrue(count($this->definitions) > 0);
    foreach ($this->patternIds as $patternId) {
      $this->assertTrue(isset($this->definitions[$patternId]));
    }
  }

  /**
   * Test the provided pattern.
   */
  public function testPattern() {
    foreach ($this->patternIds as $patternId) {
      $this->activeDefinition = $this->definitions[$patternId];
      $this->generationTest(
        [
          'parent' => 9999,
          'container' => 9999,
          'entity' => 9999,
        ]
      );
      $this->descriptionTest();
      $this->serializationTest();
      $this->deletionTest();
      $this->removalTest();
    }
  }

  /**
   * Test the provided pattern with the patchData.
   */
  public function testPatternWithData() {
    foreach ($this->patternIds as $patternId) {
      $this->activeDefinition = $this->definitions[$patternId];
      $this->generationTest($this->patchData);
      $this->descriptionTestWithData();
      $this->patchTest();
      $this->serializationTestWithData();
      $this->deletionTest();
      $this->removalTest();
    }
  }

  /**
   * Test the generate function.
   */
  protected function generationTest(array $data = []) {
    $this->entity = $this->elementHandler->generate($this->activeDefinition, $data);
    $this->assertTrue($this->entity != NULL);
    $this->assertTrue($this->entity->parent->target_id == $this->patchData['parent']);
    $this->assertTrue($this->entity->container->target_id == $this->patchData['container']);
    $this->assertTrue($this->entity->entity->target_id == $this->patchData['entity']);
  }

  /**
   * Tests the describe function.
   */
  protected function descriptionTest() {
    $description = $this->elementHandler->describe($this->entity);
    $this->assertTrue(\is_numeric($description['id']));
    $this->assertTrue($description['id'] == $this->entity->id());
    $this->assertTrue(\is_numeric($description['parent']));
    $this->assertTrue(\is_numeric($description['entity']));
    $this->assertTrue(\is_numeric($description['container']));
    $this->assertEquals($description['parent'], $this->patchData['parent']);
    $this->assertEquals($description['container'], $this->patchData['container']);
    $this->assertEquals($description['entity'], $this->patchData['entity']);
    $this->assertEquals($description['type'], $this->activeDefinition->getAdditional()['type']);
    $this->assertTrue(\is_array($description['fields']));
    return $description;
  }

  /**
   * Tests the describe function.
   */
  protected function descriptionTestWithData() {
    $description = $this->descriptionTest();
    $fields = $this->activeDefinition->getFields();
    $this->assertTrue(is_countable($description['fields']));
    $this->assertTrue(is_countable($fields));
    $this->assertTrue(count($description['fields']) >= 0);
    $this->assertTrue(count($fields) >= 0);
    $this->assertTrue(count($description['fields']) == count($fields));
    foreach ($description['fields'] as $field) {
      $this->assertNotEmpty($field);
      $this->assertTrue(\is_array($field));
      $this->assertTrue($this->activeDefinition->hasField($field['name']));
      $this->assertTrue(\is_numeric($field['id']));
      $this->assertTrue(\is_numeric($field['parent']));
      $this->assertTrue(\is_numeric($field['entity']));
      $this->assertTrue(\is_numeric($field['container']));
      $this->assertTrue($field['parent'] == $this->entity->id());
      $this->assertTrue($field['container'] == $this->patchData['container']);
      $this->assertTrue($field['entity'] == $this->patchData['entity']);
      $this->assertNotEmpty($field['type']);
    }
    return $description;
  }

  /**
   * Tests the patching function.
   */
  protected function patchTest() {
    $this->elementHandler->patch($this->entity, $this->patchData);
    $this->assertTrue($this->entity != NULL);
    $this->assertTrue($this->entity->parent->target_id == $this->patchData['parent']);
    $this->assertTrue($this->entity->container->target_id == $this->patchData['container']);
    $this->assertTrue($this->entity->entity->target_id == $this->patchData['entity']);
  }

  /**
   * Tests the serialize function.
   */
  protected function serializationTest() {
    $serialization = $this->elementHandler->serialize($this->entity);
    $this->assertTrue($serialization['id'] == $this->entity->id());
    $this->assertTrue($serialization['type'] == $this->activeDefinition->getAdditional()['type']);
    $this->assertTrue(\is_array($serialization['fields']));
    $fields = $this->activeDefinition->getFields();
    $this->assertTrue(count($serialization['fields']) == (is_countable($fields) ? count($fields) : 0));
    return $serialization;
  }

  /**
   * Tests the serialize function.
   */
  protected function serializationTestWithData() {
    $serialization = $this->serializationTest();
    foreach ($serialization['fields'] as $name => $field) {
      $this->assertNotEmpty($field);
      $this->assertTrue(\is_array($field));
      $this->assertTrue($this->activeDefinition->hasField($name));
    }
    return $serialization;
  }

  /**
   * Tests the delete function without removal.
   *
   * Asserts that the base entity's and its childrens' delete flag
   * are set to true.
   */
  protected function deletionTest() {
    $storage = \Drupal::entityTypeManager()->getStorage("pagedesigner_element");
    $elementId = $this->entity->id();

    $this->elementHandler->delete($this->entity, FALSE);
    $entity = $storage->loadUnchanged($elementId);
    foreach ($this->entity->children as $item) {
      $this->assertTrue((bool) $item->entity->deleted->value);
    }
    $this->assertTrue((bool) $entity->deleted->value);
  }

  /**
   * Tests the delete function with removal.
   *
   * Asserts that the base entity and its children are null.
   */
  protected function removalTest() {
    $storage = \Drupal::entityTypeManager()->getStorage("pagedesigner_element");
    $elementId = $this->entity->id();

    $this->elementHandler->delete($this->entity, TRUE);
    foreach ($this->entity->children as $item) {
      $this->assertNull($storage->loadUnchanged($item->entity->id()));
    }
    $this->assertNull($storage->loadUnchanged($elementId));
  }

}
