<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_runtime\Kernel;

use Drupal\Tests\flowdrop\Kernel\WorkflowExecutionTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Symfony\Component\HttpFoundation\Request;

/**
 * Pins the access model of the runtime status API.
 *
 * The status endpoints are gated by the single permission
 * "access flowdrop runtime api" with NO per-execution ownership check —
 * any holder may present any execution id. That is currently safe only
 * because StatusTracker is request-scoped in-memory state: a polling
 * request always sees an empty tracker, so foreign execution ids return
 * 404 rather than data. This test pins both halves of that contract:
 *
 * - the permission gate on every status route (and the stricter
 *   "administer flowdrop" gate on /health), and
 * - the empty-tracker 404 for execution ids not populated in the same
 *   request.
 *
 * If StatusTracker ever gains persistent backing (state, cache, a
 * long-running app server), the second half of this contract breaks and
 * the endpoints WILL leak cross-user execution metadata (statuses, node
 * errors, outputs) to every permission holder — at that point the routes
 * need an ownership check against the owning pipeline, like
 * SnapshotApiController already does with $entity->access('view').
 *
 * @group flowdrop_runtime
 */
class RuntimeStatusApiAccessTest extends WorkflowExecutionTestBase {

  use UserCreationTrait;

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

  /**
   * Status routes require the runtime API permission; health needs admin.
   */
  public function testRoutePermissionGates(): void {
    $accessManager = $this->container->get('access_manager');

    $apiUser = $this->createUser(['access flowdrop runtime api']);
    $nobody = $this->createUser([]);

    $statusRoutes = [
      'flowdrop_runtime.api.status' => ['executionId' => 'exec_x'],
      'flowdrop_runtime.api.status.nodes' => ['executionId' => 'exec_x'],
      'flowdrop_runtime.api.status.details' => ['executionId' => 'exec_x'],
      'flowdrop_runtime.api.metrics' => [],
    ];
    foreach ($statusRoutes as $route => $parameters) {
      $this->assertTrue(
        $accessManager->checkNamedRoute($route, $parameters, $apiUser),
        sprintf('"access flowdrop runtime api" grants %s.', $route),
      );
      $this->assertFalse(
        $accessManager->checkNamedRoute($route, $parameters, $nobody),
        sprintf('%s is denied without the runtime API permission.', $route),
      );
    }

    // Health exposes system internals (memory, PHP version): admin only.
    $this->assertFalse(
      $accessManager->checkNamedRoute('flowdrop_runtime.api.health', [], $apiUser),
      'The runtime API permission must not grant the health endpoint.',
    );
    $admin = $this->createUser(['administer flowdrop']);
    $this->assertTrue(
      $accessManager->checkNamedRoute('flowdrop_runtime.api.health', [], $admin),
      '"administer flowdrop" grants the health endpoint.',
    );
  }

  /**
   * An execution id not populated in this request returns 404, not data.
   *
   * This is the half of the contract that keeps the permission-only model
   * safe: the tracker is request-scoped, so a foreign (or guessed)
   * execution id never resolves to another user's run.
   */
  public function testUnknownExecutionReturnsNotFound(): void {
    $this->setCurrentUser($this->createUser(['access flowdrop runtime api']));
    $httpKernel = $this->container->get('http_kernel');

    $response = $httpKernel->handle(
      Request::create('/api/flowdrop-runtime/status/someone_elses_execution'),
    );
    $this->assertSame(404, $response->getStatusCode());
    $payload = json_decode((string) $response->getContent(), TRUE);
    $this->assertFalse($payload['success'] ?? TRUE);

    $response = $httpKernel->handle(
      Request::create('/api/flowdrop-runtime/status/someone_elses_execution/details'),
    );
    $this->assertSame(404, $response->getStatusCode());

    // Node statuses for an unknown execution are empty — no enumeration
    // value in probing ids.
    $response = $httpKernel->handle(
      Request::create('/api/flowdrop-runtime/status/someone_elses_execution/nodes'),
    );
    $payload = json_decode((string) $response->getContent(), TRUE);
    $this->assertSame([], $payload['data']['node_statuses'] ?? NULL);
  }

}
