<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_workflow\Kernel;

use Drupal\flowdrop_workflow\FlowDropWorkflowInterface;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Tests\flowdrop\Kernel\FlowDropKernelTestTrait;

/**
 * Tests access control for the FlowDropWorkflow entity.
 *
 * Verifies the create / edit / delete permission split, with both
 * 'administer flowdrop_workflow' and the global 'administer flowdrop'
 * acting as supersets.
 *
 * @group flowdrop_workflow
 * @coversDefaultClass \Drupal\flowdrop_workflow\FlowDropWorkflowAccessControlHandler
 */
class FlowDropWorkflowAccessTest extends EntityKernelTestBase {

  use FlowDropKernelTestTrait;

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

  /**
   * A workflow to run entity access checks against.
   */
  protected FlowDropWorkflowInterface $workflow;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installConfig(["system"]);

    $this->installEntitySchema('flowdrop_node_category');
    $this->installEntitySchema('flowdrop_node_type');
    $this->installEntitySchema('flowdrop_workflow');

    // Burn uid 1, which bypasses all access checks.
    $this->createUser();

    $storage = $this->entityTypeManager->getStorage('flowdrop_workflow');
    $entity = $storage->create([
      'id' => 'access_test_workflow',
      'label' => 'Access Test Workflow',
    ]);
    $entity->save();
    if (!$entity instanceof FlowDropWorkflowInterface) {
      throw new \RuntimeException('Failed to create "FlowDrop Workflow" test entity.');
    }
    $this->workflow = $entity;
  }

  /**
   * Tests a user without any FlowDrop permissions.
   */
  public function testNoPermissions(): void {
    $account = $this->createUser();
    $this->assertWorkflowAccess($account, view: FALSE, update: FALSE, delete: FALSE, create: FALSE);
  }

  /**
   * Tests that 'view' grants read-only access and nothing else.
   */
  public function testViewOnly(): void {
    $account = $this->createUser(['view flowdrop_workflow']);
    $this->assertWorkflowAccess($account, view: TRUE, update: FALSE, delete: FALSE, create: FALSE);
  }

  /**
   * Tests that 'create' grants create access and nothing else.
   */
  public function testCreateOnly(): void {
    $account = $this->createUser(['create flowdrop_workflow']);
    $this->assertWorkflowAccess($account, view: FALSE, update: FALSE, delete: FALSE, create: TRUE);
  }

  /**
   * Tests that 'edit' grants update access and nothing else.
   */
  public function testEditOnly(): void {
    $account = $this->createUser(['edit flowdrop_workflow']);
    $this->assertWorkflowAccess($account, view: FALSE, update: TRUE, delete: FALSE, create: FALSE);
  }

  /**
   * Tests that 'delete' grants delete access and nothing else.
   */
  public function testDeleteOnly(): void {
    $account = $this->createUser(['delete flowdrop_workflow']);
    $this->assertWorkflowAccess($account, view: FALSE, update: FALSE, delete: TRUE, create: FALSE);
  }

  /**
   * Tests that the split permissions compose without granting extras.
   */
  public function testCreateAndEditWithoutDelete(): void {
    $account = $this->createUser([
      'create flowdrop_workflow',
      'edit flowdrop_workflow',
    ]);
    $this->assertWorkflowAccess($account, view: FALSE, update: TRUE, delete: FALSE, create: TRUE);
  }

  /**
   * Tests that 'administer flowdrop_workflow' remains the full superset.
   */
  public function testAdministerWorkflowSuperset(): void {
    $account = $this->createUser(['administer flowdrop_workflow']);
    $this->assertWorkflowAccess($account, view: TRUE, update: TRUE, delete: TRUE, create: TRUE);
  }

  /**
   * Tests that the global 'administer flowdrop' bypasses all checks.
   */
  public function testAdministerFlowdropSuperset(): void {
    $account = $this->createUser(['administer flowdrop']);
    $this->assertWorkflowAccess($account, view: TRUE, update: TRUE, delete: TRUE, create: TRUE);
  }

  /**
   * Tests that 'execute' grants no authoring access.
   */
  public function testExecuteGrantsNoAuthoringAccess(): void {
    $account = $this->createUser(['execute flowdrop_workflow']);
    $this->assertWorkflowAccess($account, view: FALSE, update: FALSE, delete: FALSE, create: FALSE);
  }

  /**
   * Asserts the full view/update/delete/create access matrix for an account.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The account to check.
   * @param bool $view
   *   Expected result of the 'view' operation.
   * @param bool $update
   *   Expected result of the 'update' operation.
   * @param bool $delete
   *   Expected result of the 'delete' operation.
   * @param bool $create
   *   Expected result of create access.
   */
  protected function assertWorkflowAccess($account, bool $view, bool $update, bool $delete, bool $create): void {
    $this->assertSame($view, $this->workflow->access('view', $account), 'view access');
    $this->assertSame($update, $this->workflow->access('update', $account), 'update access');
    $this->assertSame($delete, $this->workflow->access('delete', $account), 'delete access');

    $handler = $this->entityTypeManager->getAccessControlHandler('flowdrop_workflow');
    $this->assertSame($create, $handler->createAccess(NULL, $account), 'create access');
  }

}
