<?php

namespace Drupal\Tests\eb\Kernel\Service;

use Drupal\eb\Entity\EbDefinition;
use Drupal\eb\Exception\ValidationException;
use Drupal\eb\Service\DefinitionFactory;
use Drupal\Tests\eb\Kernel\EbKernelTestBase;

/**
 * Kernel tests for DefinitionFactory service.
 *
 * Tests that the DefinitionFactory correctly creates definitions from
 * flat YAML data, storing bundles, fields, and displays properly.
 *
 * @coversDefaultClass \Drupal\eb\Service\DefinitionFactory
 * @group eb
 */
class DefinitionFactoryKernelTest extends EbKernelTestBase {

  /**
   * The definition factory service.
   *
   * @var \Drupal\eb\Service\DefinitionFactory
   */
  protected DefinitionFactory $definitionFactory;

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

    $this->definitionFactory = $this->container->get('eb.definition_factory');
  }

  /**
   * Tests creating a definition from flat YAML data.
   *
   * @covers ::createFromYaml
   */
  public function testCreateFromYamlFlat(): void {
    $yamlData = [
      'id' => 'test_flat',
      'label' => 'Test Flat',
      'description' => 'A test definition from flat YAML.',
      'version' => '1.0',
      'bundle_definitions' => [
        [
          'entity_type' => 'node',
          'bundle_id' => 'article',
          'label' => 'Article',
          'description' => 'An article content type.',
        ],
      ],
      'field_definitions' => [
        [
          'entity_type' => 'node',
          'bundle' => 'article',
          'field_name' => 'field_subtitle',
          'field_type' => 'string',
          'label' => 'Subtitle',
          'required' => TRUE,
        ],
        [
          'entity_type' => 'node',
          'bundle' => 'article',
          'field_name' => 'field_body',
          'field_type' => 'text_long',
          'label' => 'Body',
        ],
      ],
    ];

    $definition = $this->definitionFactory->createFromYaml($yamlData, 'test.yml');

    $this->assertNotNull($definition);
    $this->assertEquals('test_flat', $definition->id());
    $this->assertEquals('Test Flat', $definition->label());

    // Verify bundles were extracted.
    $bundles = $definition->getBundleDefinitions();
    $this->assertCount(1, $bundles, 'Should have 1 bundle.');
    $this->assertEquals('article', $bundles[0]['bundle_id']);
    $this->assertEquals('Article', $bundles[0]['label']);

    // Verify fields were extracted.
    $fields = $definition->getFieldDefinitions();
    $this->assertCount(2, $fields, 'Should have 2 fields.');

    // Check field data uses correct keys.
    $fieldNames = array_column($fields, 'field_name');
    $this->assertContains('field_subtitle', $fieldNames);
    $this->assertContains('field_body', $fieldNames);
  }

  /**
   * Tests that hierarchical format is rejected.
   *
   * @covers ::createFromYaml
   */
  public function testHierarchicalFormatRejected(): void {
    $yamlData = [
      'id' => 'test_hierarchical',
      'label' => 'Test Hierarchical',
      'entities' => [
        [
          'entity_type' => 'node',
          'bundles' => [
            [
              'bundle_id' => 'article',
              'label' => 'Article',
            ],
          ],
        ],
      ],
    ];

    $this->expectException(ValidationException::class);
    $this->expectExceptionMessageMatches('/hierarchical.*no longer supported/i');

    $this->definitionFactory->createFromYaml($yamlData, 'test.yml');
  }

  /**
   * Tests fields have entity_type from flat format.
   */
  public function testFieldsHaveEntityType(): void {
    $yamlData = [
      'name' => 'entity_type_test',
      'label' => 'Entity Type Test',
      'bundle_definitions' => [
        [
          'entity_type' => 'taxonomy_term',
          'bundle_id' => 'tags',
          'label' => 'Tags',
        ],
      ],
      'field_definitions' => [
        [
          'entity_type' => 'taxonomy_term',
          'bundle' => 'tags',
          'field_name' => 'field_color',
          'field_type' => 'string',
          'label' => 'Color',
        ],
      ],
    ];

    $definition = $this->definitionFactory->createFromYaml($yamlData, 'test.yml');
    $fields = $definition->getFieldDefinitions();

    $this->assertCount(1, $fields);
    $this->assertEquals('taxonomy_term', $fields[0]['entity_type']);
    $this->assertEquals('tags', $fields[0]['bundle']);
  }

  /**
   * Tests display field definitions are stored correctly.
   */
  public function testDisplayFieldDefinitions(): void {
    $yamlData = [
      'id' => 'display_test',
      'label' => 'Display Test',
      'bundle_definitions' => [
        [
          'entity_type' => 'node',
          'bundle_id' => 'page',
          'label' => 'Page',
        ],
      ],
      'field_definitions' => [
        [
          'entity_type' => 'node',
          'bundle' => 'page',
          'field_name' => 'field_summary',
          'field_type' => 'string',
          'label' => 'Summary',
        ],
      ],
      'display_field_definitions' => [
        [
          'entity_type' => 'node',
          'bundle' => 'page',
          'display_type' => 'form',
          'mode' => 'default',
          'field_name' => 'field_summary',
          'widget' => 'string_textfield',
          'weight' => 0,
        ],
        [
          'entity_type' => 'node',
          'bundle' => 'page',
          'display_type' => 'view',
          'mode' => 'default',
          'field_name' => 'field_summary',
          'formatter' => 'string',
          'weight' => 0,
        ],
      ],
    ];

    $definition = $this->definitionFactory->createFromYaml($yamlData, 'test.yml');
    $displays = $definition->getDisplayFieldDefinitions();

    // Should have form and view displays.
    $this->assertCount(2, $displays);

    // Check if we have both form and view type displays.
    $types = array_column($displays, 'display_type');
    $this->assertContains('form', $types, 'Should have form display.');
    $this->assertContains('view', $types, 'Should have view display.');
  }

  /**
   * Tests updating an existing definition from YAML.
   *
   * @covers ::updateFromYaml
   */
  public function testUpdateFromYaml(): void {
    // Create initial definition.
    $initialData = [
      'id' => 'update_test',
      'label' => 'Update Test',
      'bundle_definitions' => [
        [
          'entity_type' => 'node',
          'bundle_id' => 'original',
          'label' => 'Original',
        ],
      ],
      'field_definitions' => [
        [
          'entity_type' => 'node',
          'bundle' => 'original',
          'field_name' => 'field_original',
          'field_type' => 'string',
          'label' => 'Original Field',
        ],
      ],
    ];

    $definition = $this->definitionFactory->createFromYaml($initialData, 'update_test.yml');
    $definition->save();

    // Update with new data.
    $updatedData = [
      'id' => 'update_test',
      'label' => 'Updated Test',
      'bundle_definitions' => [
        [
          'entity_type' => 'node',
          'bundle_id' => 'original',
          'label' => 'Updated Original',
        ],
      ],
      'field_definitions' => [
        [
          'entity_type' => 'node',
          'bundle' => 'original',
          'field_name' => 'field_original',
          'field_type' => 'string',
          'label' => 'Original Field',
        ],
        [
          'entity_type' => 'node',
          'bundle' => 'original',
          'field_name' => 'field_new',
          'field_type' => 'text_long',
          'label' => 'New Field',
        ],
      ],
    ];

    $existing = EbDefinition::load('update_test');
    $this->assertNotNull($existing, 'Definition should exist after save.');
    $updated = $this->definitionFactory->updateFromYaml($existing, $updatedData);

    $this->assertEquals('Updated Test', $updated->label());
    $this->assertCount(2, $updated->getFieldDefinitions(), 'Should have 2 fields after update.');

    $bundles = $updated->getBundleDefinitions();
    $this->assertEquals('Updated Original', $bundles[0]['label']);
  }

  /**
   * Tests definition ID generation from file path.
   */
  public function testDefinitionIdFromFilePath(): void {
    $yamlData = [
      'label' => 'File Path Test',
      'bundle_definitions' => [],
    ];

    // Create from a path with directory structure.
    $definition = $this->definitionFactory->createFromYaml($yamlData, '/path/to/my_definition.yml');

    $this->assertEquals('my_definition', $definition->id());
  }

  /**
   * Tests definition with explicit id overrides file path.
   */
  public function testExplicitIdOverridesFilePath(): void {
    $yamlData = [
      'id' => 'explicit_name',
      'label' => 'Explicit Name Test',
      'bundle_definitions' => [],
    ];

    $definition = $this->definitionFactory->createFromYaml($yamlData, '/path/to/different_name.yml');

    $this->assertEquals('explicit_name', $definition->id());
  }

  /**
   * Tests menu definitions are extracted correctly.
   */
  public function testMenuDefinitionsExtracted(): void {
    $yamlData = [
      'name' => 'menu_test',
      'label' => 'Menu Test',
      'bundle_definitions' => [],
      'menu_definitions' => [
        [
          'menu_id' => 'footer',
          'label' => 'Footer Menu',
          'description' => 'Links in the footer.',
        ],
      ],
    ];

    $definition = $this->definitionFactory->createFromYaml($yamlData, 'test.yml');
    $menus = $definition->getMenuDefinitions();

    $this->assertCount(1, $menus);
    $this->assertEquals('footer', $menus[0]['menu_id']);
    $this->assertEquals('Footer Menu', $menus[0]['label']);
  }

  /**
   * Tests entity types are retrieved from bundle definitions.
   */
  public function testEntityTypesFromBundles(): void {
    $yamlData = [
      'name' => 'entity_types_test',
      'label' => 'Entity Types Test',
      'bundle_definitions' => [
        [
          'entity_type' => 'media',
          'bundle_id' => 'image',
          'label' => 'Image',
        ],
        [
          'entity_type' => 'node',
          'bundle_id' => 'gallery',
          'label' => 'Gallery',
        ],
      ],
    ];

    $definition = $this->definitionFactory->createFromYaml($yamlData, 'test.yml');

    // Should return unique entity types.
    $entityTypes = $definition->getEntityTypes();
    $this->assertCount(2, $entityTypes);
    $this->assertContains('media', $entityTypes);
    $this->assertContains('node', $entityTypes);
  }

  /**
   * Tests loading a definition.
   *
   * @covers ::loadDefinition
   */
  public function testLoadDefinition(): void {
    $definition = EbDefinition::create([
      'id' => 'load_test',
      'label' => 'Load Test',
    ]);
    $definition->save();

    $loaded = $this->definitionFactory->loadDefinition('load_test');
    $this->assertNotNull($loaded);
    $this->assertEquals('Load Test', $loaded->label());

    $nonexistent = $this->definitionFactory->loadDefinition('nonexistent');
    $this->assertNull($nonexistent);
  }

  /**
   * Tests multiple bundles across entity types.
   */
  public function testMultipleBundlesAcrossEntityTypes(): void {
    $yamlData = [
      'name' => 'multi_entity_test',
      'label' => 'Multi Entity Test',
      'bundle_definitions' => [
        [
          'entity_type' => 'node',
          'bundle_id' => 'article',
          'label' => 'Article',
        ],
        [
          'entity_type' => 'node',
          'bundle_id' => 'page',
          'label' => 'Page',
        ],
        [
          'entity_type' => 'taxonomy_term',
          'bundle_id' => 'tags',
          'label' => 'Tags',
        ],
      ],
    ];

    $definition = $this->definitionFactory->createFromYaml($yamlData, 'test.yml');
    $bundles = $definition->getBundleDefinitions();

    $this->assertCount(3, $bundles, 'Should have 3 bundles total.');

    // Verify entity types are preserved.
    $entityTypes = array_unique(array_column($bundles, 'entity_type'));
    $this->assertContains('node', $entityTypes);
    $this->assertContains('taxonomy_term', $entityTypes);
  }

  /**
   * Tests field group definitions.
   */
  public function testFieldGroupDefinitions(): void {
    $yamlData = [
      'id' => 'field_group_test',
      'label' => 'Field Group Test',
      'bundle_definitions' => [
        [
          'entity_type' => 'node',
          'bundle_id' => 'article',
          'label' => 'Article',
        ],
      ],
      'field_group_definitions' => [
        [
          'entity_type' => 'node',
          'bundle' => 'article',
          'display_type' => 'form',
          'mode' => 'default',
          'group_name' => 'group_content',
          'label' => 'Content',
          'format_type' => 'fieldset',
        ],
      ],
    ];

    $definition = $this->definitionFactory->createFromYaml($yamlData, 'test.yml');
    $fieldGroups = $definition->getFieldGroupDefinitions();

    $this->assertCount(1, $fieldGroups);
    $this->assertEquals('group_content', $fieldGroups[0]['group_name']);
    $this->assertEquals('Content', $fieldGroups[0]['label']);
  }

}
