<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_node_type\Kernel;

use Drupal\flowdrop_node_type\FlowDropNodeTypeInterface;
use Drupal\KernelTests\KernelTestBase;

/**
 * Guards shipped node-type config against stale / hand-edited dependencies.
 *
 * Dependencies on a config entity are a snapshot recomputed only on a non-sync
 * save; config sync (module install) imports them verbatim. So a shipped
 * install file whose `dependencies` block drifts from what
 * calculateDependencies() would produce silently ships the wrong dependency
 * graph — exactly the failure that let the slim-metadata work re-resolve a
 * node's executor plugin while the stored config recorded no module
 * dependency at all.
 *
 * Two things this proves:
 * - Installing the module's config succeeds, i.e. no shipped node type declares
 *   a module dependency outside its module's dependency closure (the foreach
 *   node type mis-homed under flowdrop_node_processor would fail here).
 * - Every installed node type's stored dependencies match a fresh
 *   recalculation.
 *
 * @group flowdrop_node_type
 */
class ConfigDependencyIntegrityTest extends KernelTestBase {

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

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('flowdrop_node_type');
    // Seeds every flowdrop_node_processor node type and validates each one's
    // declared dependencies against the installed module set; throws
    // UnmetDependenciesException if any dep is outside the closure.
    $this->installConfig(['flowdrop_node_processor']);
  }

  /**
   * Shipped node-type dependencies are not stale relative to a fresh recalc.
   */
  public function testShippedNodeTypeDependenciesAreNotStale(): void {
    $storage = $this->container->get('entity_type.manager')
      ->getStorage('flowdrop_node_type');
    $entities = $storage->loadMultiple();
    $this->assertNotEmpty($entities, 'flowdrop_node_processor must ship node types.');

    foreach ($entities as $id => $entity) {
      $this->assertInstanceOf(FlowDropNodeTypeInterface::class, $entity);
      $stored = $entity->getDependencies();
      $fresh = $entity->calculateDependencies()->getDependencies();
      $this->assertSame(
        $stored,
        $fresh,
        "Stored dependencies for node type '{$id}' drifted from a fresh "
        . 'calculateDependencies() — re-export its config/install file.',
      );
    }
  }

  /**
   * Every node-processor plugin id is namespaced as "{provider}:{original_id}".
   *
   * This is the invariant FlowDropNodeType::calculateDependencies() relies on:
   * it derives the module dependency from the first segment of the executor
   * plugin id by string-split, *not* a plugin-manager lookup (which would be
   * unreliable mid install/uninstall). That is correct only because
   * FlowDropNodeProcessorPluginManager constructs every id — derivatives
   * included — as "{provider}:{original_id}", so the prefix always equals the
   * real provider. If a discovery change ever lets a bare id or a prefix that
   * diverges from the provider through, this fails and the string-split (and
   * the "no provider lookup needed" decision) must be revisited.
   */
  public function testPluginIdPrefixAlwaysEqualsProvider(): void {
    $manager = $this->container->get('flowdrop.node_processor_plugin_manager');
    $definitions = $manager->getDefinitions();
    $this->assertNotEmpty($definitions, 'There must be node-processor plugins to check.');

    foreach ($definitions as $id => $definition) {
      $provider = $definition['provider'] ?? NULL;
      $this->assertNotNull($provider, "Plugin '{$id}' must declare a provider.");
      $this->assertStringContainsString(
        ':',
        (string) $id,
        "Plugin id '{$id}' is not namespaced; the dependency string-split assumes a '{provider}:…' id.",
      );
      $this->assertSame(
        $provider,
        explode(':', (string) $id, 2)[0],
        "Plugin id '{$id}' prefix does not match its provider '{$provider}'; "
        . 'FlowDropNodeType::calculateDependencies() would derive the wrong module dependency.',
      );
    }
  }

}
