<?php

declare(strict_types=1);

namespace Drupal\native_observability_execution\EventSubscriber;

use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\native_observability\Service\RequestLogExclusionMatcher;
use Drupal\native_observability_execution\Service\ExecutionSpanStack;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Records a root span for each main HTTP request.
 *
 * Phase 2 MVP:
 * - Measure total request duration (REQUEST -> RESPONSE)
 * - Store the span in the in-memory span stack (current request)
 * - Persist a small rolling history in PrivateTempStore for cross-request
 *   visibility.
 *
 * IMPORTANT:
 * - This is intentionally lightweight and safe (no headers/body capture).
 * - Storage is per-user via PrivateTempStore.
 */
final class ExecutionRequestSubscriber implements EventSubscriberInterface {

  /**
   * TempStore key used to persist recent request spans.
   */
  private const STORE_COLLECTION = 'native_observability_execution';
  private const STORE_KEY_RECENT_SPANS = 'recent_request_spans';

  /**
   * Request start time (microtime(true)).
   *
   * @var float|null
   */
  private ?float $start = NULL;

  public function __construct(
    private readonly ExecutionSpanStack $spanStack,
    private readonly PrivateTempStoreFactory $tempStoreFactory,
  ) {
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      KernelEvents::REQUEST => ['onRequest', 0],
      KernelEvents::RESPONSE => ['onResponse', 0],
    ];
  }

  /**
   * Captures the start time for the main request.
   */
  public function onRequest(RequestEvent $event): void {
    if (!$event->isMainRequest()) {
      return;
    }
    if (RequestLogExclusionMatcher::isRequestExcluded($event->getRequest())) {
      return;
    }
    $this->start = microtime(TRUE);
  }

  /**
   * Computes duration and stores the root request span.
   */
  public function onResponse(ResponseEvent $event): void {
    if (!$event->isMainRequest()) {
      return;
    }

    if (RequestLogExclusionMatcher::isRequestExcluded($event->getRequest())) {
      $this->spanStack->reset();
      $this->start = NULL;
      return;
    }

    // If we never started timing (unexpected), do nothing.
    if ($this->start === NULL) {
      return;
    }

    $request = $event->getRequest();
    $response = $event->getResponse();

    $method = strtoupper($request->getMethod());
    $path = $request->getPathInfo();
    $status = (int) $response->getStatusCode();

    $durationMs = (int) round((microtime(TRUE) - $this->start) * 1000);

    // Collect all spans recorded during this request, including
    // external.http and service-level spans.
    $spans = $this->spanStack->all();

    // Append the root request span last, so it always exists in the dataset.
    // This is the anchor span for the current request lifecycle.
    $spans[] = [
      'category' => 'http.request',
      'method' => $method,
      'path' => $path,
      'status' => $status,
      'duration_ms' => $durationMs,
      'ts' => (int) round(microtime(TRUE) * 1000),
    ];

    // Persist spans to TempStore so the Execution UI can show results
    // across requests.
    $store = $this->tempStoreFactory->get(self::STORE_COLLECTION);
    $history = $store->get(self::STORE_KEY_RECENT_SPANS) ?? [];

    foreach ($spans as $span) {
      $history[] = $span;
    }

    // No hard limit here: pagination in the UI will handle browsing.
    $store->set(self::STORE_KEY_RECENT_SPANS, $history);

    // Reset per-request state.
    $this->spanStack->reset();
    $this->start = NULL;
  }

}
