<?php

declare(strict_types=1);

namespace Drupal\Tests\ai_ckeditor\Kernel;

use Drupal\ai_ckeditor\Controller\AiRequest;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;

/**
 * Tests AiRequest::validateEntityContext() against real entities.
 *
 * @group ai_ckeditor
 * @coversDefaultClass \Drupal\ai_ckeditor\Controller\AiRequest
 */
class ValidateEntityContextTest extends EntityKernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'filter',
    'editor',
    'ckeditor5',
    'node',
    'ai',
    'ai_ckeditor',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installEntitySchema('node');
    $this->installSchema('node', ['node_access']);
    $this->installConfig(['user', 'filter']);
    Role::load(RoleInterface::ANONYMOUS_ID)
      ->grantPermission('access content')
      ->save();
  }

  /**
   * Invokes the protected validateEntityContext() via reflection.
   */
  protected function invokeValidate(string $entity_type, string $entity_id): ?array {
    $controller = AiRequest::create($this->container);
    $method = new \ReflectionMethod(AiRequest::class, 'validateEntityContext');
    return $method->invoke($controller, $entity_type, $entity_id);
  }

  /**
   * Content entity (node): integer ID resolves and bundle is read from entity.
   *
   * @covers ::validateEntityContext
   */
  public function testContentEntityIntegerId(): void {
    NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
    $node = Node::create(['type' => 'article', 'title' => 'Test']);
    $node->save();

    $result = $this->invokeValidate('node', (string) $node->id());

    $this->assertIsArray($result);
    $this->assertSame('node', $result['entity_type']);
    $this->assertSame('article', $result['bundle']);
    $this->assertSame((string) $node->id(), $result['id']);
  }

  /**
   * Config entity (node_type): string ID resolves; bundle equals type id.
   *
   * @covers ::validateEntityContext
   */
  public function testConfigEntityStringId(): void {
    NodeType::create(['type' => 'page', 'name' => 'Page'])->save();

    $result = $this->invokeValidate('node_type', 'page');

    $this->assertIsArray($result);
    $this->assertSame('node_type', $result['entity_type']);
    // node_type is a bundle-less config entity; bundle() returns the
    // entity type id itself.
    $this->assertSame('node_type', $result['bundle']);
    $this->assertSame('page', $result['id']);
  }

  /**
   * Non-existent entity returns NULL.
   *
   * @covers ::validateEntityContext
   */
  public function testNonExistentEntityReturnsNull(): void {
    $this->assertNull($this->invokeValidate('node', '99999'));
  }

  /**
   * Unknown entity type returns NULL.
   *
   * @covers ::validateEntityContext
   */
  public function testUnknownEntityTypeReturnsNull(): void {
    $this->assertNull($this->invokeValidate('not_a_real_entity_type', '1'));
  }

  /**
   * Empty inputs return NULL.
   *
   * @covers ::validateEntityContext
   */
  public function testEmptyInputsReturnNull(): void {
    $this->assertNull($this->invokeValidate('', '1'));
    $this->assertNull($this->invokeValidate('node', ''));
  }

  /**
   * An entity the current user cannot view is rejected.
   *
   * Entity context must never leak context about entities the requesting
   * user has no access to, so validateEntityContext() checks view access
   * on the loaded entity.
   *
   * @covers ::validateEntityContext
   */
  public function testEntityWithoutViewAccessReturnsNull(): void {
    NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
    $node = Node::create([
      'type' => 'article',
      'title' => 'Unpublished test',
      'status' => 0,
    ]);
    $node->save();

    // Anonymous users may access content, but not unpublished content.
    $this->assertNull($this->invokeValidate('node', (string) $node->id()));
  }

  /**
   * An entity the current user can view resolves normally.
   *
   * @covers ::validateEntityContext
   */
  public function testUnpublishedEntityResolvesForOwnerWithPermission(): void {
    NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
    // The first created user gets uid 1, which bypasses access checks, so
    // burn it to test with a regular user.
    $this->drupalCreateUser();
    $owner = $this->drupalCreateUser([
      'access content',
      'view own unpublished content',
    ]);
    $node = Node::create([
      'type' => 'article',
      'title' => 'Unpublished test',
      'status' => 0,
      'uid' => $owner->id(),
    ]);
    $node->save();

    $this->container->get('current_user')->setAccount($owner);
    $result = $this->invokeValidate('node', (string) $node->id());

    $this->assertIsArray($result);
    $this->assertSame('article', $result['bundle']);
  }

}
