<?php

declare(strict_types=1);

namespace Drupal\nodehive_path_alias\EventSubscriber;

use Drupal\Core\Database\Connection;
use Drupal\decoupled_router\PathTranslatorEvent;
use Drupal\nodehive_path_alias\PathAliasSpaceLookupInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

use function sprintf;

/**
 * Resolves path aliases by the NodeHive space API token in the Bearer header.
 *
 * Priority 90 — runs after SpaceIdPathTranslatorSubscriber (100) but before
 * the core RouterPathTranslatorSubscriber (0).
 *
 * The token is matched against the 'apikey' column of the nodehive_space table
 * (the per-space key generated when a space is created, stored in plain text).
 * This is distinct from nodehive_api_key_management's nhk_ user-level tokens.
 *
 * Frontend usage:
 *   GET /router/translate-path?path=/about
 *   Authorization: Bearer <space-apikey>
 *
 * The space apikey is exposed on the space's edit form (API tab) and can be
 * stored as NODEHIVE_SPACE_API_KEY in the Next.js environment.
 */
final class ApiTokenPathTranslatorSubscriber implements EventSubscriberInterface {

  public function __construct(
    private readonly Connection $database,
    private readonly PathAliasSpaceLookupInterface $lookup,
    private readonly LoggerInterface $logger,
  ) {}

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      PathTranslatorEvent::TRANSLATE => ['onPathTranslation', 90],
    ];
  }

  /**
   * Resolves the path translation using the Bearer token space API key.
   */
  public function onPathTranslation(PathTranslatorEvent $event): void {
    $request = $event->getRequest();

    // Skip if a higher-priority subscriber already resolved the space.
    if ($request->attributes->get('_nodehive_space_resolved')) {
      return;
    }

    $token = $this->extractBearerToken($request->headers->get('Authorization', ''));
    if ($token === NULL) {
      return;
    }

    $spaceId = $this->lookupSpaceByApiKey($token);
    if ($spaceId === NULL) {
      $this->logger->warning(
        'Bearer token does not match any space API key. Path translation skipped for path "@path".',
        ['@path' => $event->getPath()],
      );

      return;
    }

    $alias = $this->cleanAlias($event->getPath(), $request->getBasePath());
    $langcode = $request->query->get('langcode') ?: NULL;

    $systemPath = $this->lookup->resolve($alias, $spaceId, $langcode);
    if ($systemPath === NULL) {
      $this->logger->notice(
        'Path alias "@alias" not found in space @space_id (langcode: @langcode). No system path resolved.',
        [
          '@alias' => $alias,
          '@space_id' => $spaceId,
          '@langcode' => $langcode ?? 'any',
        ],
      );

      return;
    }

    $event->setPath($systemPath);
    $event->getResponse()
      ->getCacheableMetadata()
      ->addCacheContexts(['request_format', 'url.query_args:langcode', 'headers:Authorization'])
      ->addCacheTags(['nodehive_space_list']);
  }

  /**
   * Extracts the bearer token value from an Authorization header string.
   */
  private function extractBearerToken(string $authHeader): ?string {
    if (!str_starts_with(strtolower($authHeader), 'bearer ')) {
      return NULL;
    }
    $token = trim(substr($authHeader, 7));

    return $token !== '' ? $token : NULL;
  }

  /**
   * Looks up the space integer ID by its plain-text apikey value.
   */
  private function lookupSpaceByApiKey(string $token): ?int {
    $result = $this->database->select('nodehive_space', 'ns')
      ->fields('ns', ['id'])
      ->condition('apikey', $token)
      ->range(0, 1)
      ->execute()
      ->fetchField();

    return $result !== FALSE ? (int) $result : NULL;
  }

  /**
   * Strips the base path prefix from a path alias string.
   */
  private function cleanAlias(string $path, string $basePath): string {
    if ($basePath === '') {
      return $path;
    }

    return preg_replace(sprintf('/^%s/', preg_quote($basePath, '/')), '', $path);
  }

}
