<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop\Unit\DTO\NodeMetadata;

use Drupal\Tests\UnitTestCase;
use Drupal\flowdrop\DTO\NodeMetadata\NodeMetadata;
use Drupal\flowdrop\DTO\NodeMetadata\NodePort;
use Drupal\flowdrop\DTO\ConfigEdit\ConfigEdit;
use Drupal\flowdrop\DTO\ConfigEdit\DynamicSchemaEndpoint;

/**
 * Tests the NodeMetadata DTO.
 *
 * @coversDefaultClass \Drupal\flowdrop\DTO\NodeMetadata\NodeMetadata
 * @group flowdrop
 */
class NodeMetadataTest extends UnitTestCase {

  /**
   * Creates a sample NodeMetadata for testing.
   */
  private function createSampleMetadata(): NodeMetadata {
    return new NodeMetadata(
      id: "test_node",
      name: "Test Node",
      type: "basic",
      supportedTypes: ["basic", "compact"],
      description: "A test node",
      category: "processing",
      icon: "mdi:cog",
      pluginVersion: "1.0.0",
      enabled: TRUE,
      tags: ["test", "sample"],
      executorPlugin: "test_executor",
      inputs: [
        NodePort::input("input_1", "Input 1", "string", TRUE, "Required input"),
        NodePort::input("input_2", "Input 2", "number", FALSE),
      ],
      outputs: [
        NodePort::output("output_1", "Output 1", "json"),
      ],
      config: ["timeout" => 30],
      configSchema: ["type" => "object"],
    );
  }

  /**
   * @covers ::__construct
   */
  public function testConstructor(): void {
    $metadata = $this->createSampleMetadata();
    $this->assertEquals("test_node", $metadata->id);
    $this->assertEquals("Test Node", $metadata->name);
    $this->assertTrue($metadata->enabled);
    $this->assertEquals(["test", "sample"], $metadata->tags);
  }

  /**
   * @covers ::getInputById
   */
  public function testGetInputById(): void {
    $metadata = $this->createSampleMetadata();
    $port = $metadata->getInputById("input_1");
    $this->assertNotNull($port);
    $this->assertEquals("Input 1", $port->name);
    $this->assertNull($metadata->getInputById("nonexistent"));
  }

  /**
   * @covers ::getOutputById
   */
  public function testGetOutputById(): void {
    $metadata = $this->createSampleMetadata();
    $port = $metadata->getOutputById("output_1");
    $this->assertNotNull($port);
    $this->assertEquals("Output 1", $port->name);
    $this->assertNull($metadata->getOutputById("nonexistent"));
  }

  /**
   * @covers ::getInputCount
   * @covers ::getOutputCount
   */
  public function testPortCounts(): void {
    $metadata = $this->createSampleMetadata();
    $this->assertEquals(2, $metadata->getInputCount());
    $this->assertEquals(1, $metadata->getOutputCount());
  }

  /**
   * @covers ::hasRequiredInputs
   * @covers ::getRequiredInputs
   */
  public function testRequiredInputs(): void {
    $metadata = $this->createSampleMetadata();
    $this->assertTrue($metadata->hasRequiredInputs());
    $required = $metadata->getRequiredInputs();
    $this->assertCount(1, $required);
  }

  /**
   * @covers ::hasConfigEdit
   */
  public function testHasConfigEditFalse(): void {
    $metadata = $this->createSampleMetadata();
    $this->assertFalse($metadata->hasConfigEdit());
  }

  /**
   * @covers ::hasConfigEdit
   */
  public function testHasConfigEditTrue(): void {
    $metadata = new NodeMetadata(
      id: "test",
      name: "Test",
      type: "basic",
      supportedTypes: [],
      description: "",
      category: "",
      icon: "",
      pluginVersion: "1.0",
      enabled: TRUE,
      tags: [],
      executorPlugin: "",
      inputs: [],
      outputs: [],
      config: [],
      configSchema: [],
      configEdit: new ConfigEdit(
        dynamicSchema: new DynamicSchemaEndpoint(url: "/api/schema"),
      ),
    );
    $this->assertTrue($metadata->hasConfigEdit());
  }

  /**
   * @covers ::toArray
   */
  public function testToArray(): void {
    $metadata = $this->createSampleMetadata();
    $array = $metadata->toArray();
    $this->assertEquals("test_node", $array["node_type_id"]);
    $this->assertEquals("Test Node", $array["name"]);
    $this->assertCount(2, $array["inputs"]);
    $this->assertCount(1, $array["outputs"]);
    $this->assertArrayNotHasKey("configEdit", $array);
    // No uiSchema on the sample → omitted, keeping the editor on its flat path.
    $this->assertArrayNotHasKey("uiSchema", $array);
  }

  /**
   * The uiSchema is serialized only when present, and round-trips intact.
   *
   * @covers ::toArray
   * @covers ::fromArray
   */
  public function testUiSchemaSerialization(): void {
    $uiSchema = [
      "type" => "VerticalLayout",
      "elements" => [
        ["type" => "Control", "scope" => "#/properties/model"],
        [
          "type" => "Group",
          "label" => "Execution",
          "collapsible" => TRUE,
          "defaultOpen" => FALSE,
          "elements" => [
            ["type" => "Control", "scope" => "#/properties/maxRetries"],
          ],
        ],
      ],
    ];
    $metadata = new NodeMetadata(
      id: "with_ui",
      name: "With UI",
      type: "basic",
      supportedTypes: ["basic"],
      description: "",
      category: "processing",
      icon: "mdi:cog",
      pluginVersion: "1.0.0",
      enabled: TRUE,
      tags: [],
      executorPlugin: "test_executor",
      inputs: [],
      outputs: [],
      config: [],
      configSchema: ["type" => "object"],
      configEdit: NULL,
      uiSchema: $uiSchema,
    );

    $array = $metadata->toArray();
    $this->assertSame($uiSchema, $array["uiSchema"]);

    $restored = NodeMetadata::fromArray($array);
    $this->assertSame($uiSchema, $restored->uiSchema);
  }

  /**
   * @covers ::fromArray
   */
  public function testFromArray(): void {
    $data = [
      "node_type_id" => "from_array_node",
      "name" => "From Array",
      "type" => "terminal",
      "supportedTypes" => ["terminal"],
      "description" => "Created from array",
      "category" => "output",
      "icon" => "mdi:stop",
      "plugin_version" => "2.0",
      "enabled" => FALSE,
      "tags" => ["output"],
      "executor_plugin" => "my_executor",
      "inputs" => [
        ["id" => "in_1", "name" => "In 1", "type" => "input", "dataType" => "string"],
      ],
      "outputs" => [],
      "config" => ["key" => "val"],
      "configSchema" => ["type" => "object"],
    ];

    $metadata = NodeMetadata::fromArray($data);
    $this->assertEquals("from_array_node", $metadata->id);
    $this->assertEquals("terminal", $metadata->type);
    $this->assertFalse($metadata->enabled);
    $this->assertEquals(1, $metadata->getInputCount());
  }

  /**
   * @covers ::toArray
   * @covers ::fromArray
   */
  public function testRoundTrip(): void {
    $metadata = $this->createSampleMetadata();
    $restored = NodeMetadata::fromArray($metadata->toArray());
    $this->assertEquals($metadata->toArray(), $restored->toArray());
  }

}
