<?php

declare(strict_types=1);

namespace Drupal\native_observability_spans\EventSubscriber;

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

/**
 * Writes one synthetic HTTP request summary span when enrichment is available.
 *
 * The subscriber does not hard-depend on any specific observability module.
 * Enrichment providers are injected through a tagged iterator so feature
 * modules can optionally contribute extra request-level metadata.
 */
final class RequestSummarySpanSubscriber implements EventSubscriberInterface {

  /**
   * Creates a new request summary span subscriber instance.
   *
   * @param \Drupal\native_observability_spans\Service\SpanStorageInterface $spanStorage
   *   The span storage service.
   * @param \Drupal\native_observability\Service\RequestIdGeneratorInterface $requestIdGenerator
   *   The request ID generator used to create synthetic span identifiers.
   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
   *   The request stack.
   * @param iterable<\Drupal\native_observability_spans\Service\RequestSummaryEnrichmentProviderInterface> $requestSummaryEnrichmentProviders
   *   The tagged request summary enrichment providers.
   */
  public function __construct(
    private readonly SpanStorageInterface $spanStorage,
    private readonly RequestIdGeneratorInterface $requestIdGenerator,
    private readonly RequestStack $requestStack,
    private readonly iterable $requestSummaryEnrichmentProviders,
  ) {
  }

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

  /**
   * Writes one synthetic request summary span when enrichment exists.
   *
   * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
   *   The kernel response event.
   */
  public function onKernelResponse(ResponseEvent $event): void {
    if (!$event->isMainRequest()) {
      return;
    }

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

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

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

    $response = $event->getResponse();
    $enrichmentPayload = [];
    $hasEnrichment = FALSE;

    foreach ($this->requestSummaryEnrichmentProviders as $provider) {
      if (!$provider instanceof RequestSummaryEnrichmentProviderInterface) {
        continue;
      }

      try {
        $providerPayload = $provider->buildRequestSummaryPayload($request, $response);

        if (is_array($providerPayload) && $providerPayload !== []) {
          $enrichmentPayload = array_replace_recursive($enrichmentPayload, $providerPayload);
          $hasEnrichment = TRUE;
        }
      }
      catch (\Throwable) {
        // Fail-safe: never break the response pipeline for observability.
      }
    }

    if (!$hasEnrichment) {
      return;
    }

    $startedAt = (float) ($request->attributes->get(TraceSubscriber::ATTR_STARTED_AT) ?? microtime(TRUE));
    $endedAtMs = (int) round(microtime(TRUE) * 1000);
    $startedAtMs = (int) round($startedAt * 1000);
    $durationMs = max(0, $endedAtMs - $startedAtMs);

    $payload = [
      'synthetic' => TRUE,
      'summary_type' => 'http_request_summary',
      'request' => $this->buildRequestPayload($request, $response),
    ];

    $payload = array_replace_recursive($payload, $enrichmentPayload);

    try {
      $this->spanStorage->insert([
        'trace_id' => 0,
        'request_id' => $requestId,
        'span_id' => $this->requestIdGenerator->generate(),
        'parent_span_id' => NULL,
        'name' => 'http.request_summary',
        'category' => 'http',
        'started_at_ms' => $startedAtMs,
        'ended_at_ms' => $endedAtMs,
        'duration_ms' => $durationMs,
        'payload' => json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
      ]);
    }
    catch (\Throwable) {
      // Fail-safe: never break the response pipeline for observability.
    }
  }

  /**
   * Builds the base request payload stored in the synthetic summary span.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request.
   * @param \Symfony\Component\HttpFoundation\Response $response
   *   The final response.
   *
   * @return array
   *   The normalized request payload.
   */
  private function buildRequestPayload(Request $request, Response $response): array {
    return [
      'method' => strtoupper($request->getMethod()),
      'path' => $request->getPathInfo() ?: '/',
      'route_name' => (string) ($request->attributes->get('_route') ?? ''),
      'status_code' => (int) $response->getStatusCode(),
      'is_ajax' => $request->isXmlHttpRequest(),
    ];
  }

}
