<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_interrupt\Kernel;

use Drupal\Core\Form\FormState;
use Drupal\Core\Session\AccountInterface;
use Drupal\Tests\flowdrop\Kernel\WorkflowExecutionTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface;
use Drupal\flowdrop_interrupt\Enum\InterruptStatus;
use Drupal\flowdrop_interrupt\Enum\InterruptType;
use Drupal\flowdrop_interrupt\Form\DismissInterruptsConfirmForm;

/**
 * Pins the bulk dismiss form to the operator's cancel access.
 *
 * The inbox only queues the operator's own interrupts, but the tempstore
 * handover between the inbox and the confirm form must not be the
 * boundary: the confirm form re-checks cancel access per interrupt at
 * the point of consumption, so a stale, tampered, or future producer of
 * dismiss_ids can never dismiss an interrupt the operator could not
 * cancel directly.
 *
 * @group flowdrop_interrupt
 */
class InterruptBulkDismissScopeTest extends WorkflowExecutionTestBase {

  use UserCreationTrait;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    // Burn uid 1: the superuser bypasses all access checks.
    $this->createUser();
  }

  /**
   * Creates a pending interrupt owned by the given account.
   */
  protected function createInterruptOwnedBy(AccountInterface $owner): FlowDropInterruptInterface {
    $storage = $this->container->get('entity_type.manager')
      ->getStorage('flowdrop_interrupt');
    /** @var \Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface $interrupt */
    $interrupt = $storage->create([
      'type' => InterruptType::Confirmation->value,
      'workflow_id' => 'dismiss_fixture',
      'node_id' => 'confirm.1',
      'message' => 'Proceed?',
      'status' => InterruptStatus::Pending->value,
      'uid' => $owner->id(),
    ]);
    $interrupt->save();
    return $interrupt;
  }

  /**
   * Queues UUIDs in the current user's tempstore and submits the form.
   *
   * @param array<int, string> $uuids
   *   The interrupt UUIDs to queue for dismissal.
   */
  protected function submitDismissal(array $uuids): void {
    $this->container->get('tempstore.private')
      ->get('flowdrop_interrupt')
      ->set('dismiss_ids', $uuids);

    $form_state = new FormState();
    $this->container->get('form_builder')
      ->submitForm(DismissInterruptsConfirmForm::class, $form_state);
  }

  /**
   * Reloads an interrupt and returns whether it is still pending.
   */
  protected function isPending(FlowDropInterruptInterface $interrupt): bool {
    $storage = $this->container->get('entity_type.manager')
      ->getStorage('flowdrop_interrupt');
    $storage->resetCache([$interrupt->id()]);
    /** @var \Drupal\flowdrop_interrupt\Entity\FlowDropInterruptInterface $fresh */
    $fresh = $storage->load($interrupt->id());
    return $fresh->isPending();
  }

  /**
   * Cancel-own dismisses own interrupts only — foreign UUIDs are skipped.
   *
   * Simulates a tampered/stale dismiss_ids queue carrying a foreign UUID:
   * the access re-check at consumption must skip it.
   */
  public function testCancelOwnCannotDismissForeignInterrupts(): void {
    $other = $this->createUser([]);
    $foreign = $this->createInterruptOwnedBy($other);

    $operator = $this->createUser(['cancel own flowdrop interrupts']);
    $this->setCurrentUser($operator);
    $own = $this->createInterruptOwnedBy($operator);

    $this->submitDismissal([(string) $own->uuid(), (string) $foreign->uuid()]);

    $this->assertFalse($this->isPending($own),
      'The operator\'s own interrupt was dismissed.');
    $this->assertTrue($this->isPending($foreign),
      'A foreign interrupt in the dismissal queue must be skipped for cancel-own operators.');
  }

  /**
   * Cancel-any dismisses foreign interrupts.
   */
  public function testCancelAnyDismissesForeignInterrupts(): void {
    $other = $this->createUser([]);
    $foreign = $this->createInterruptOwnedBy($other);

    $operator = $this->createUser(['cancel any flowdrop interrupts']);
    $this->setCurrentUser($operator);

    $this->submitDismissal([(string) $foreign->uuid()]);

    $this->assertFalse($this->isPending($foreign),
      'Cancel-any operators may dismiss foreign interrupts.');
  }

  /**
   * View permissions never dismiss — even the operator's own interrupts.
   */
  public function testViewPermissionsNeverDismiss(): void {
    $operator = $this->createUser([
      'view own flowdrop interrupts',
      'view any flowdrop interrupts',
    ]);
    $this->setCurrentUser($operator);
    $own = $this->createInterruptOwnedBy($operator);

    $this->submitDismissal([(string) $own->uuid()]);

    $this->assertTrue($this->isPending($own),
      'View permissions must not dismiss interrupts, ownership notwithstanding.');
  }

}
