<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_node_type\Kernel;

use Drupal\flowdrop_node_type\Entity\FlowDropNodeType;
use Drupal\flowdrop_node_type\FlowDropNodeTypeInterface;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\flowdrop\Kernel\FlowDropKernelTestTrait;

/**
 * Tests the FlowDropNodeType entity creation and storage.
 *
 * @group flowdrop_node_type
 * @coversDefaultClass \Drupal\flowdrop_node_type\Entity\FlowDropNodeType
 */
class FlowDropNodeTypeKernelTest extends EntityKernelTestBase {

  use FlowDropKernelTestTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    "system",
    "user",
    'entity_test',
    'flowdrop_node_category',
    'flowdrop_node_type',
    'flowdrop',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installConfig(["system"]);

    $this->installEntitySchema('flowdrop_node_category');
    $this->installEntitySchema('flowdrop_node_type');

    // Disable schema checking for tests to avoid validation errors.
    $this->config('system.logging')->set('error_level', 'hide')->save();
  }

  /**
   * Tests entity property setters and getters.
   *
   * Note: Color is no longer stored on node types. It is inherited from
   * the node's category.
   *
   * @covers ::setLabel
   * @covers ::setDescription
   * @covers ::setCategory
   * @covers ::setIcon
   * @covers ::setPluginVersion
   * @covers ::setEnabled
   * @covers ::setParameters
   * @covers ::setTags
   * @covers ::setExecutorPlugin
   */
  public function testEntityPropertySettersAndGetters(): void {
    // Create a real entity using the entity storage system.
    $entity = $this->createTestEntity();

    // Test all setters and getters.
    $entity->setLabel("Updated Label");
    $this->assertEquals("Updated Label", $entity->getLabel());

    $entity->setDescription("Updated Description");
    $this->assertEquals("Updated Description", $entity->getDescription());

    $entity->setCategory("input");
    $this->assertEquals("input", $entity->getCategory());

    $entity->setIcon("mdi:input");
    $this->assertEquals("mdi:input", $entity->getIcon());

    $entity->setPluginVersion("2.0.0");
    $this->assertEquals("2.0.0", $entity->getPluginVersion());

    $entity->setEnabled(FALSE);
    $this->assertFalse($entity->isEnabled());

    $parameters = [
      "test_param" => [
        "configurable" => TRUE,
        "connectable" => FALSE,
        "required" => FALSE,
      ],
    ];
    $entity->setParameters($parameters);
    $this->assertEquals($parameters, $entity->getParameters());

    $tags = ["test", "tags"];
    $entity->setTags($tags);
    $this->assertEquals($tags, $entity->getTags());

    $entity->setExecutorPlugin("test_plugin");
    $this->assertEquals("test_plugin", $entity->getExecutorPlugin());
  }

  /**
   * Tests entity property type handling.
   *
   * @covers ::setParameters
   * @covers ::setTags
   */
  public function testEntityPropertyTypeHandling(): void {
    $entity = $this->createTestEntity();

    // Test with different data types in parameters.
    $complex_parameters = [
      "string_param" => [
        "configurable" => TRUE,
        "connectable" => FALSE,
        "required" => TRUE,
        "default" => "value",
      ],
      "number_param" => [
        "configurable" => TRUE,
        "connectable" => TRUE,
        "required" => FALSE,
        "default" => 42,
      ],
      "boolean_param" => [
        "configurable" => FALSE,
        "connectable" => TRUE,
        "required" => FALSE,
        "default" => TRUE,
      ],
    ];

    $entity->setParameters($complex_parameters);
    $this->assertEquals($complex_parameters, $entity->getParameters());

    // Tags are a string list (setTags() expects array<string>); verify the
    // round-trip preserves varied string forms.
    $tags = ["string", "123", "special-chars_and.dots", ""];
    $entity->setTags($tags);
    $this->assertEquals($tags, $entity->getTags());
  }

  /**
   * Tests default values are set correctly.
   *
   * Note: Color is no longer stored on node types. It is inherited from
   * the node's category.
   *
   * @covers ::__construct
   */
  public function testDefaultValues(): void {
    $entity = $this->createTestEntity();

    // Test default values.
    $this->assertEquals("", $entity->getDescription());
    $this->assertEquals("processing", $entity->getCategory());
    $this->assertEquals("mdi:cog", $entity->getIcon());
    $this->assertEquals("", $entity->getPluginVersion());
    $this->assertTrue($entity->isEnabled());
    $this->assertEquals([], $entity->getParameters());
    $this->assertEquals([], $entity->getTags());
    $this->assertEquals("", $entity->getExecutorPlugin());
  }

  /**
   * Tests entity property validation.
   *
   * Note: Color is no longer stored on node types. It is inherited from
   * the node's category.
   *
   * @covers ::setLabel
   * @covers ::setDescription
   * @covers ::setCategory
   * @covers ::setIcon
   * @covers ::setPluginVersion
   * @covers ::setEnabled
   * @covers ::setParameters
   * @covers ::setTags
   * @covers ::setExecutorPlugin
   */
  public function testEntityPropertyValidation(): void {
    $entity = $this->createTestEntity();

    // Test that properties can handle various input types.
    $entity->setLabel("Test Label");
    $this->assertEquals("Test Label", $entity->getLabel());

    $entity->setDescription("Test Description");
    $this->assertEquals("Test Description", $entity->getDescription());

    $entity->setCategory("input");
    $this->assertEquals("input", $entity->getCategory());

    $entity->setIcon("mdi:test");
    $this->assertEquals("mdi:test", $entity->getIcon());

    $entity->setPluginVersion("2.1.0");
    $this->assertEquals("2.1.0", $entity->getPluginVersion());

    $entity->setEnabled(FALSE);
    $this->assertFalse($entity->isEnabled());

    $entity->setEnabled(TRUE);
    $this->assertTrue($entity->isEnabled());

    $entity->setParameters(["test_param" => ["configurable" => TRUE]]);
    $this->assertEquals(["test_param" => ["configurable" => TRUE]], $entity->getParameters());

    $entity->setTags(["tag1", "tag2"]);
    $this->assertEquals(["tag1", "tag2"], $entity->getTags());

    $entity->setExecutorPlugin("test_executor");
    $this->assertEquals("test_executor", $entity->getExecutorPlugin());
  }

  /**
   * Tests entity property chaining.
   *
   * Note: Color is no longer stored on node types. It is inherited from
   * the node's category.
   *
   * @covers ::setLabel
   * @covers ::setDescription
   * @covers ::setCategory
   * @covers ::setIcon
   * @covers ::setPluginVersion
   * @covers ::setEnabled
   * @covers ::setParameters
   * @covers ::setTags
   * @covers ::setExecutorPlugin
   */
  public function testEntityPropertyChaining(): void {
    $entity = $this->createTestEntity();

    // Test that setters return the entity instance for chaining.
    $result = $entity->setLabel("Chained Label");
    $this->assertSame($entity, $result);

    $result = $entity->setDescription("Chained Description");
    $this->assertSame($entity, $result);

    $result = $entity->setCategory("output");
    $this->assertSame($entity, $result);

    $result = $entity->setIcon("mdi:output");
    $this->assertSame($entity, $result);

    $result = $entity->setPluginVersion("3.0.0");
    $this->assertSame($entity, $result);

    $result = $entity->setEnabled(FALSE);
    $this->assertSame($entity, $result);

    $result = $entity->setParameters(["chained_param" => ["configurable" => TRUE]]);
    $this->assertSame($entity, $result);

    $result = $entity->setTags(["chained", "tags"]);
    $this->assertSame($entity, $result);

    $result = $entity->setExecutorPlugin("chained_executor");
    $this->assertSame($entity, $result);
  }

  /**
   * Tests entity property edge cases.
   *
   * Note: Color is no longer stored on node types. It is inherited from
   * the node's category.
   *
   * @covers ::setLabel
   * @covers ::setDescription
   * @covers ::setCategory
   * @covers ::setIcon
   * @covers ::setPluginVersion
   * @covers ::setEnabled
   * @covers ::setParameters
   * @covers ::setTags
   * @covers ::setExecutorPlugin
   */
  public function testEntityPropertyEdgeCases(): void {
    $entity = $this->createTestEntity();

    // Test empty strings.
    $entity->setLabel("");
    $this->assertEquals("", $entity->getLabel());

    $entity->setDescription("");
    $this->assertEquals("", $entity->getDescription());

    $entity->setCategory("");
    $this->assertEquals("", $entity->getCategory());

    $entity->setIcon("");
    $this->assertEquals("", $entity->getIcon());

    $entity->setPluginVersion("");
    $this->assertEquals("", $entity->getPluginVersion());

    $entity->setExecutorPlugin("");
    $this->assertEquals("", $entity->getExecutorPlugin());

    // Test empty arrays.
    $entity->setParameters([]);
    $this->assertEquals([], $entity->getParameters());

    $entity->setTags([]);
    $this->assertEquals([], $entity->getTags());
  }

  /**
   * Tests entity creation and storage.
   *
   * @covers ::save
   * @covers ::load
   */
  public function testEntityCreationAndStorage(): void {
    $entity = $this->createTestEntity();

    // Save the entity.
    $entity->save();
    $this->assertNotEmpty($entity->uuid());

    // Load the entity from storage.
    $loaded_entity = FlowDropNodeType::load("test_entity");
    $this->assertInstanceOf(FlowDropNodeType::class, $loaded_entity);
    $this->assertEquals("test_entity", $loaded_entity->id());
    $this->assertEquals("Test Entity", $loaded_entity->label());
  }

  /**
   * Tests entity update functionality.
   *
   * @covers ::save
   */
  public function testEntityUpdate(): void {
    $entity = $this->createTestEntity();
    $entity->save();

    // Update the entity.
    $entity->setLabel("Updated Label");
    $entity->setDescription("Updated description");
    $entity->setCategory("input");
    $entity->setEnabled(FALSE);
    $entity->setParameters([]);
    $entity->setTags(["updated", "tags"]);
    $entity->setExecutorPlugin("updated_executor");

    $entity->save();

    // Load and verify updates.
    $updated_entity = FlowDropNodeType::load("test_entity");
    $this->assertEquals("Updated Label", $updated_entity->label());
    $this->assertEquals("Updated description", $updated_entity->getDescription());
    $this->assertEquals("input", $updated_entity->getCategory());
    $this->assertFalse($updated_entity->isEnabled());
    $this->assertEquals([], $updated_entity->getParameters());
    $this->assertEquals(["updated", "tags"], $updated_entity->getTags());
    $this->assertEquals("updated_executor", $updated_entity->getExecutorPlugin());
  }

  /**
   * Tests entity deletion.
   *
   * @covers ::delete
   */
  public function testEntityDeletion(): void {
    $entity = $this->createTestEntity();
    $entity->save();
    $this->assertInstanceOf(FlowDropNodeType::class, FlowDropNodeType::load("test_entity"));

    $entity->delete();
    $this->assertNull(FlowDropNodeType::load("test_entity"));
  }

  /**
   * Tests the toNodeDefinition method.
   *
   * Note: Color is no longer stored on node types. It is inherited from
   * the node's category. The toNodeDefinition method no longer includes
   * the color field.
   *
   * @covers ::toNodeDefinition
   */
  public function testToNodeDefinition(): void {
    $entity = $this->createTestEntity();

    // Set up the entity with test data.
    $entity->setLabel("Definition Test Node");
    $entity->setDescription("Test node definition");
    $entity->setCategory("processing");
    $entity->setIcon("mdi:cog");
    $entity->setPluginVersion("1.0.0");
    $entity->setEnabled(TRUE);
    $entity->setParameters([]);
    $entity->setTags(["definition", "test"]);
    $entity->setExecutorPlugin("test_executor");

    // Save the entity to ensure it's properly initialized.
    $entity->save();

    $definition = $entity->toNodeDefinition();

    $this->assertEquals("test_entity", $definition["id"]);
    $this->assertEquals("Definition Test Node", $definition["name"]);
    $this->assertEquals("Test node definition", $definition["description"]);
    $this->assertEquals("processing", $definition["category"]);
    $this->assertEquals("mdi:cog", $definition["icon"]);
    // Color is inherited from the category, not stored on the node type.
    $this->assertArrayNotHasKey("color", $definition);
    $this->assertEquals("1.0.0", $definition["plugin_version"]);
    $this->assertTrue($definition["enabled"]);
    $this->assertEquals([], $definition["parameters"]);
    $this->assertEquals(["definition", "test"], $definition["tags"]);
  }

  /**
   * Tests configuration export and import.
   *
   * Note: Color is no longer stored on node types. It is inherited from
   * the node's category.
   *
   * @covers ::toArray
   * @covers ::create
   */
  public function testConfigurationExportAndImport(): void {
    $original_entity = $this->createTestEntity();

    // Set up the entity with test data.
    $original_entity->setLabel("Config Test Node");
    $original_entity->setDescription("Testing config export/import");
    $original_entity->setCategory("output");
    $original_entity->setIcon("mdi:output");
    $original_entity->setPluginVersion("3.0.0");
    $original_entity->setEnabled(FALSE);
    $original_entity->setParameters([]);
    $original_entity->setTags(["config", "export", "import"]);
    $original_entity->setExecutorPlugin("config_executor");

    // Export configuration.
    $exported_config = $original_entity->toArray();

    // Import configuration.
    $imported_entity = FlowDropNodeType::create($exported_config);

    // Verify imported entity matches original.
    $this->assertEquals("test_entity", $imported_entity->id());
    $this->assertEquals("Config Test Node", $imported_entity->label());
    $this->assertEquals("Testing config export/import", $imported_entity->getDescription());
    $this->assertEquals("output", $imported_entity->getCategory());
    $this->assertEquals("mdi:output", $imported_entity->getIcon());
    // Color is inherited from the category, not stored on the node type.
    $this->assertEquals("3.0.0", $imported_entity->getPluginVersion());
    $this->assertFalse($imported_entity->isEnabled());
    $this->assertEquals([], $imported_entity->getParameters());
    $this->assertEquals(["config", "export", "import"], $imported_entity->getTags());
    $this->assertEquals("config_executor", $imported_entity->getExecutorPlugin());
  }

  /**
   * Tests parameter-specific methods.
   *
   * @covers ::getParameter
   * @covers ::setParameter
   */
  public function testParameterMethods(): void {
    $entity = $this->createTestEntity();

    // Test setting individual parameters.
    $entity->setParameter("input_text", [
      "configurable" => TRUE,
      "connectable" => TRUE,
      "required" => TRUE,
      "default" => "hello",
    ]);

    $entity->setParameter("temperature", [
      "configurable" => TRUE,
      "connectable" => FALSE,
      "required" => FALSE,
      "default" => 0.7,
    ]);

    // Test getting individual parameters.
    $input_text_config = $entity->getParameter("input_text");
    $this->assertIsArray($input_text_config);
    $this->assertTrue($input_text_config["configurable"]);
    $this->assertTrue($input_text_config["connectable"]);
    $this->assertTrue($input_text_config["required"]);
    $this->assertEquals("hello", $input_text_config["default"]);

    $temperature_config = $entity->getParameter("temperature");
    $this->assertIsArray($temperature_config);
    $this->assertTrue($temperature_config["configurable"]);
    $this->assertFalse($temperature_config["connectable"]);
    $this->assertEquals(0.7, $temperature_config["default"]);

    // Test getting non-existent parameter.
    $this->assertNull($entity->getParameter("nonexistent"));

    // Test that all parameters are stored correctly.
    $all_params = $entity->getParameters();
    $this->assertCount(2, $all_params);
    $this->assertArrayHasKey("input_text", $all_params);
    $this->assertArrayHasKey("temperature", $all_params);
  }

  /**
   * Tests output-specific methods.
   *
   * @covers ::getOutput
   * @covers ::setOutput
   * @covers ::getOutputs
   * @covers ::setOutputs
   */
  public function testOutputMethods(): void {
    $entity = $this->createTestEntity();

    // Test setting individual outputs.
    $entity->setOutput("data", ["exposed" => TRUE]);
    $entity->setOutput("debug", ["exposed" => FALSE]);

    // Test getting individual outputs.
    $data_config = $entity->getOutput("data");
    $this->assertIsArray($data_config);
    $this->assertTrue($data_config["exposed"]);

    $debug_config = $entity->getOutput("debug");
    $this->assertIsArray($debug_config);
    $this->assertFalse($debug_config["exposed"]);

    // Test getting non-existent output.
    $this->assertNull($entity->getOutput("nonexistent"));

    // Test setting all outputs at once.
    $all_outputs = [
      "result" => ["exposed" => TRUE],
      "metadata" => ["exposed" => TRUE],
    ];
    $entity->setOutputs($all_outputs);
    $this->assertEquals($all_outputs, $entity->getOutputs());
  }

  /**
   * Tests unified port methods.
   *
   * @covers ::isUnifiedInputExposed
   * @covers ::setUnifiedInputExposed
   * @covers ::isUnifiedOutputExposed
   * @covers ::setUnifiedOutputExposed
   */
  public function testUnifiedPortMethods(): void {
    $entity = $this->createTestEntity();

    // Test default values (should be FALSE).
    $this->assertFalse($entity->isUnifiedInputExposed());
    $this->assertFalse($entity->isUnifiedOutputExposed());

    // Test setting unified input port.
    $result = $entity->setUnifiedInputExposed(TRUE);
    $this->assertSame($entity, $result);
    $this->assertTrue($entity->isUnifiedInputExposed());

    $entity->setUnifiedInputExposed(FALSE);
    $this->assertFalse($entity->isUnifiedInputExposed());

    // Test setting unified output port.
    $result = $entity->setUnifiedOutputExposed(TRUE);
    $this->assertSame($entity, $result);
    $this->assertTrue($entity->isUnifiedOutputExposed());

    $entity->setUnifiedOutputExposed(FALSE);
    $this->assertFalse($entity->isUnifiedOutputExposed());
  }

  /**
   * Tests unified port persistence.
   *
   * @covers ::isUnifiedInputExposed
   * @covers ::setUnifiedInputExposed
   * @covers ::isUnifiedOutputExposed
   * @covers ::setUnifiedOutputExposed
   */
  public function testUnifiedPortPersistence(): void {
    $entity = $this->createTestEntity();

    // Set unified port values.
    $entity->setUnifiedInputExposed(TRUE);
    $entity->setUnifiedOutputExposed(TRUE);
    $entity->save();

    // Load from storage and verify persistence.
    $loaded_entity = FlowDropNodeType::load("test_entity");
    $this->assertTrue($loaded_entity->isUnifiedInputExposed());
    $this->assertTrue($loaded_entity->isUnifiedOutputExposed());

    // Update to FALSE and verify.
    $loaded_entity->setUnifiedInputExposed(FALSE);
    $loaded_entity->setUnifiedOutputExposed(FALSE);
    $loaded_entity->save();

    $reloaded_entity = FlowDropNodeType::load("test_entity");
    $this->assertFalse($reloaded_entity->isUnifiedInputExposed());
    $this->assertFalse($reloaded_entity->isUnifiedOutputExposed());
  }

  /**
   * Tests unified port configuration export.
   *
   * @covers ::toArray
   */
  public function testUnifiedPortExport(): void {
    $entity = $this->createTestEntity();
    $entity->setUnifiedInputExposed(TRUE);
    $entity->setUnifiedOutputExposed(TRUE);

    $exported_config = $entity->toArray();

    $this->assertArrayHasKey("unified_input_exposed", $exported_config);
    $this->assertArrayHasKey("unified_output_exposed", $exported_config);
    $this->assertTrue($exported_config["unified_input_exposed"]);
    $this->assertTrue($exported_config["unified_output_exposed"]);
  }

  /**
   * Tests tool exposure methods.
   *
   * @covers ::isToolExposed
   * @covers ::setToolExposed
   */
  public function testToolExposedMethods(): void {
    $entity = $this->createTestEntity();

    // Default is FALSE — existing node types are unaffected.
    $this->assertFalse($entity->isToolExposed());

    $result = $entity->setToolExposed(TRUE);
    $this->assertSame($entity, $result);
    $this->assertTrue($entity->isToolExposed());

    $entity->setToolExposed(FALSE);
    $this->assertFalse($entity->isToolExposed());
  }

  /**
   * Tests tool exposure persistence and export.
   *
   * @covers ::isToolExposed
   * @covers ::setToolExposed
   * @covers ::toArray
   */
  public function testToolExposedPersistenceAndExport(): void {
    $entity = $this->createTestEntity();
    $entity->setToolExposed(TRUE);
    $entity->save();

    $loaded_entity = FlowDropNodeType::load("test_entity");
    $this->assertTrue($loaded_entity->isToolExposed());

    $exported_config = $loaded_entity->toArray();
    $this->assertArrayHasKey("tool_exposed", $exported_config);
    $this->assertTrue($exported_config["tool_exposed"]);

    $loaded_entity->setToolExposed(FALSE);
    $loaded_entity->save();
    $this->assertFalse(FlowDropNodeType::load("test_entity")->isToolExposed());
  }

  /**
   * Creates a test entity using the entity storage system.
   *
   * @return \Drupal\flowdrop_node_type\FlowDropNodeTypeInterface
   *   A test entity instance.
   */
  private function createTestEntity(): FlowDropNodeTypeInterface {
    $storage = $this->entityTypeManager->getStorage('flowdrop_node_type');
    $entity = $storage->create([
      'id' => 'test_entity',
      'label' => 'Test Entity',
    ]);
    if (!$entity instanceof FlowDropNodeTypeInterface) {
      throw new \RuntimeException('Failed to create "FlowDrop Node Type" test entity.');
    }
    return $entity;
  }

}
