<?php

declare(strict_types=1);

namespace Drupal\Tests\nodehive_core\Kernel;

use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\nodehive_core\Entity\Space;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\Group;

/**
 * Tests that nodes belonging to a space can be queried via node.nodehive_space.
 *
 * These tests verify the replacement pattern for the removed space->content
 * field: instead of $space->get('content'), use an entity query with
 * ->condition('nodehive_space', $space->id()).
 *
 * These tests are designed to pass both before and after the removal of
 * nodehive_space.content — they confirm the query pattern is sound.
 */
#[CoversNothing]
#[Group('nodehive_core')]
class SpaceContentQueryTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   *
   * @var string[]
   */
  protected static $modules = [
    'system',
    'user',
    'field',
    'text',
    'filter',
    'node',
    'link',
    'options',
    'image',
    'file',
    'taxonomy',
    'menu_ui',
    'nodehive_core',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('nodehive_space');
    $this->installEntitySchema('node');
    $this->installEntitySchema('user');
    $this->installSchema('node', ['node_access']);

    NodeType::create(['type' => 'page', 'name' => 'Page'])->save();
  }

  /**
   * Tests that nodes are returned by a query on their space.
   */
  public function testNodesQueryableBySpace(): void {
    $space = $this->createSpace('My Space');
    $node = $this->createNode('Page A', [$space->id()]);

    $ids = $this->queryNodeIdsForSpace((int) $space->id());

    self::assertCount(1, $ids);
    self::assertContains((int) $node->id(), $ids);
  }

  /**
   * Tests that nodes from space A do not appear in space B's query result.
   */
  public function testNodesNotInOtherSpaces(): void {
    $space_a = $this->createSpace('Space A');
    $space_b = $this->createSpace('Space B');
    $this->createNode('Belongs to A', [$space_a->id()]);

    $ids = $this->queryNodeIdsForSpace((int) $space_b->id());

    self::assertEmpty($ids);
  }

  /**
   * Tests that a count query returns the correct number of nodes in a space.
   */
  public function testNodeCountBySpace(): void {
    $space = $this->createSpace('Counting Space');
    $this->createNode('Node 1', [$space->id()]);
    $this->createNode('Node 2', [$space->id()]);
    $this->createNode('Node 3', [$space->id()]);

    $count = (int) \Drupal::entityQuery('node')
      ->accessCheck(FALSE)
      ->condition('nodehive_space', $space->id())
      ->count()
      ->execute();

    self::assertSame(3, $count);
  }

  /**
   * Tests that clearing a node's space removes it from the query result.
   */
  public function testNodeRemovedFromSpace(): void {
    $space = $this->createSpace('Update Space');
    $node = $this->createNode('Removable Node', [$space->id()]);

    self::assertCount(1, $this->queryNodeIdsForSpace((int) $space->id()));

    $node->set('nodehive_space', []);
    $node->save();

    self::assertCount(0, $this->queryNodeIdsForSpace((int) $space->id()));
  }

  /**
   * Tests that deleting a node removes it from the space query result.
   */
  public function testNodeDeletedDisappearsFromSpace(): void {
    $space = $this->createSpace('Delete Space');
    $node = $this->createNode('Deletable Node', [$space->id()]);

    self::assertCount(1, $this->queryNodeIdsForSpace((int) $space->id()));

    $node->delete();

    self::assertCount(0, $this->queryNodeIdsForSpace((int) $space->id()));
  }

  /**
   * Tests that a node referencing multiple spaces appears in all their results.
   */
  public function testMultipleSpacesOnOneNode(): void {
    $space_a = $this->createSpace('Multi A');
    $space_b = $this->createSpace('Multi B');
    $node = $this->createNode('Shared Node', [(int) $space_a->id(), (int) $space_b->id()]);

    $ids_a = $this->queryNodeIdsForSpace((int) $space_a->id());
    $ids_b = $this->queryNodeIdsForSpace((int) $space_b->id());

    self::assertContains((int) $node->id(), $ids_a);
    self::assertContains((int) $node->id(), $ids_b);
  }

  /**
   * Tests that querying an empty space returns an empty array.
   */
  public function testSpaceWithNoNodes(): void {
    $space = $this->createSpace('Empty Space');

    self::assertEmpty($this->queryNodeIdsForSpace((int) $space->id()));
  }

  /**
   * Creates a space entity.
   */
  private function createSpace(string $label): Space {
    $space = Space::create([
      'label' => $label,
      'machine_name' => strtolower(preg_replace('/\s+/', '_', $label)),
    ]);
    $space->save();
    return $space;
  }

  /**
   * Creates a node referencing the given space IDs.
   *
   * @param string $title
   *   Node title.
   * @param int[] $space_ids
   *   IDs of spaces this node belongs to.
   */
  private function createNode(string $title, array $space_ids): Node {
    $node = Node::create([
      'type' => 'page',
      'title' => $title,
      'status' => 1,
      'nodehive_space' => array_map(static fn(int $id) => ['target_id' => $id], $space_ids),
    ]);
    $node->save();
    return $node;
  }

  /**
   * Returns node IDs belonging to a space via entity query.
   *
   * This is the canonical replacement for $space->get('content')->getValue().
   *
   * @param int $space_id
   *   The space entity ID.
   *
   * @return int[]
   *   Node IDs.
   */
  private function queryNodeIdsForSpace(int $space_id): array {
    $result = \Drupal::entityQuery('node')
      ->accessCheck(FALSE)
      ->condition('nodehive_space', $space_id)
      ->execute();

    return array_values(array_map('intval', $result));
  }

}
