<?php

declare(strict_types=1);

namespace Drupal\native_observability_spans\EventSubscriber;

use Drupal\native_observability\EventSubscriber\TraceSubscriber;
use Drupal\native_observability\Service\RequestLogExclusionMatcher;
use Drupal\native_observability\Service\TraceStorageInterface;
use Drupal\native_observability_spans\Service\SpanStorageInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Links spans to the final trace row once the response is available.
 *
 * Spans can be recorded before the trace row exists. We backfill
 * trace_id on spans using request_id as the stable correlation key.
 */
final class SpanTraceLinkSubscriber implements EventSubscriberInterface {

  public function __construct(
    private readonly RequestStack $requestStack,
    private readonly TraceStorageInterface $traceStorage,
    private readonly SpanStorageInterface $spanStorage,
  ) {
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      KernelEvents::RESPONSE => ['onKernelResponse', -1000],
    ];
  }

  /**
   * Backfills trace_id on spans for the current request.
   */
  public function onKernelResponse(ResponseEvent $event): void {
    if (!$event->isMainRequest()) {
      return;
    }

    $request = $this->requestStack->getCurrentRequest();
    if (!$request) {
      return;
    }

    if (RequestLogExclusionMatcher::isRequestExcluded($request)) {
      return;
    }

    $request_id = (string) ($request->attributes->get(TraceSubscriber::ATTR_REQUEST_ID) ?? '');
    if ($request_id === '') {
      return;
    }

    try {
      $trace = $this->traceStorage->loadLatestByRequestId($request_id);
      if (!empty($trace['id'])) {
        $this->spanStorage->attachTraceId($request_id, (int) $trace['id']);
      }
    }
    catch (\Throwable) {
      // Fail-safe: never break the response pipeline.
    }
  }

}
