<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_workflow\Kernel;

use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\flowdrop_workflow\DTO\BundleImportResult;
use Drupal\flowdrop_workflow\Service\BundleExporter;
use Drupal\flowdrop_workflow\WorkflowDefinitionInterface;

/**
 * Tests bundle export and the export -> import round trip.
 *
 * @group flowdrop_workflow
 * @covers \Drupal\flowdrop_workflow\Service\BundleExporter
 */
class BundleExporterTest extends EntityKernelTestBase {

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

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installConfig(['system']);
    $this->installEntitySchema('flowdrop_node_category');
    $this->installEntitySchema('flowdrop_node_type');
    $this->installEntitySchema('flowdrop_workflow');
    $this->config('system.logging')->set('error_level', 'hide')->save();

    // The node type entity is the authoritative source for a node's executor
    // plugin now that slim storage no longer persists it; export re-resolves
    // `requires` from here. In production it always exists; create it here.
    $this->container->get('entity_type.manager')
      ->getStorage('flowdrop_node_type')
      ->create([
        'id' => 'calculator',
        'label' => 'Calculator',
        'enabled' => TRUE,
        'executor_plugin' => 'flowdrop_node_processor:calculator',
      ])->save();
  }

  /**
   * Returns the exporter service.
   */
  private function exporter(): BundleExporter {
    return $this->container->get('flowdrop_workflow.bundle_exporter');
  }

  /**
   * A calculator node with full metadata.
   *
   * @return array<string, mixed>
   *   The node.
   */
  private function calculatorNode(): array {
    return [
      'id' => 'calc.1',
      'data' => [
        'metadata' => [
          'node_type_id' => 'calculator',
          'executor_plugin' => 'flowdrop_node_processor:calculator',
          'name' => 'Calculator',
          'inputs' => [['id' => 'values', 'name' => 'Values', 'dataType' => 'array']],
          'configSchema' => [
            'type' => 'object',
            'properties' => [
              'operation' => ['type' => 'string', 'default' => 'add'],
              'precision' => ['type' => 'integer', 'default' => 2],
            ],
            'required' => [],
          ],
          'config' => ['operation' => 'add', 'precision' => 2],
          'outputs' => [['id' => 'result', 'name' => 'Result', 'dataType' => 'number']],
        ],
      ],
    ];
  }

  /**
   * Creates and saves a workflow with the given nodes.
   *
   * @param array<int, array<string, mixed>> $nodes
   *   The nodes.
   *
   * @return \Drupal\flowdrop_workflow\WorkflowDefinitionInterface
   *   The saved workflow.
   */
  private function createWorkflow(array $nodes): WorkflowDefinitionInterface {
    $workflow = $this->container->get('entity_type.manager')
      ->getStorage('flowdrop_workflow')
      ->create([
        'id' => 'exported_flow',
        'label' => 'Exported Flow',
        'description' => 'A flow to export.',
        'nodes' => $nodes,
        'edges' => [],
      ]);
    $workflow->save();
    assert($workflow instanceof WorkflowDefinitionInterface);
    return $workflow;
  }

  /**
   * The exported envelope is unsigned and carries the flow plus requirements.
   */
  public function testExportProducesUnsignedEnvelope(): void {
    $workflow = $this->createWorkflow([$this->calculatorNode()]);

    $bundle = $this->exporter()->export($workflow);

    $this->assertSame(BundleExporter::FORMAT, $bundle['format']);
    $this->assertNull($bundle['signature']);
    $this->assertSame('', $bundle['payload']['publisher']);
    $this->assertSame(['flowdrop_node_processor:calculator'], $bundle['payload']['requires']);
    $this->assertSame('exported_flow', $bundle['payload']['flow']['id']);
    $this->assertCount(1, $bundle['payload']['flow']['nodes']);
  }

  /**
   * An exported bundle re-imports cleanly once the original is removed.
   */
  public function testExportImportRoundTrip(): void {
    $workflow = $this->createWorkflow([$this->calculatorNode()]);
    $bundle = $this->exporter()->export($workflow);

    // Remove the original so the re-import does not collide on id.
    $workflow->delete();

    // The exported bundle is unsigned, so import requires the override.
    $result = $this->container->get('flowdrop_workflow.bundle_importer')
      ->import($bundle, FALSE, TRUE);

    $this->assertSame(BundleImportResult::APPLIED, $result->getStatus());
    $this->assertSame('exported_flow', $result->getWorkflowId());
    $this->assertNotNull(
      $this->container->get('entity_type.manager')
        ->getStorage('flowdrop_workflow')
        ->load('exported_flow')
    );
  }

}
