<?php

declare(strict_types=1);

namespace Drupal\native_observability_spans\EventSubscriber;

use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\native_observability\EventSubscriber\TraceSubscriber;
use Drupal\native_observability\Service\RequestIdGeneratorInterface;
use Drupal\native_observability_spans\Service\SpanStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Optionally creates one synthetic database summary span per request.
 *
 * This subscriber never hard-depends on the database observer module.
 * If the module is not enabled, spans continue to work normally.
 */
final class DatabaseSummarySpanSubscriber implements EventSubscriberInterface {

  /**
   * Database observer runtime collector service ID.
   */
  private const DATABASE_RUNTIME_COLLECTOR_SERVICE_ID = 'Drupal\\native_observability_database_observer\\Service\\DatabaseQueryRuntimeCollector';

  /**
   * Creates a new database summary span subscriber instance.
   */
  public function __construct(
    private readonly SpanStorageInterface $spanStorage,
    private readonly RequestIdGeneratorInterface $requestIdGenerator,
    private readonly ModuleHandlerInterface $moduleHandler,
    private readonly ContainerInterface $container,
    private readonly RequestStack $requestStack,
  ) {
  }

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

  /**
   * Writes one synthetic database summary span when the observer is available.
   */
  public function onKernelResponse(ResponseEvent $event): void {
    if (!$event->isMainRequest()) {
      return;
    }

    if (!$this->moduleHandler->moduleExists('native_observability_database_observer')) {
      return;
    }

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

    $summary = $request->attributes->get('native_observability.database.summary');

    if (
      (!is_array($summary) || $summary === [])
      && $this->container->has(self::DATABASE_RUNTIME_COLLECTOR_SERVICE_ID)
    ) {
      $runtimeCollector = $this->container->get(self::DATABASE_RUNTIME_COLLECTOR_SERVICE_ID);

      if (is_object($runtimeCollector) && method_exists($runtimeCollector, 'getRequestSummary')) {
        $runtimeSummary = $runtimeCollector->getRequestSummary();
        if (is_array($runtimeSummary) && $runtimeSummary !== []) {
          $summary = $runtimeSummary;
        }
      }
    }

    if (!is_array($summary) || $summary === []) {
      return;
    }

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

    $totalQueryTimeMs = (float) ($summary['total_query_time_ms'] ?? 0);
    $endedAtMs = (int) round(microtime(TRUE) * 1000);
    $durationMs = max(0, (int) round($totalQueryTimeMs));
    $startedAtMs = max(0, $endedAtMs - $durationMs);

    $payload = [
      'synthetic' => TRUE,
      'summary_type' => 'database_request_summary',
      'database' => $summary,
    ];

    try {
      $this->spanStorage->insert([
        'trace_id' => 0,
        'request_id' => $requestId,
        'span_id' => $this->requestIdGenerator->generate(),
        'parent_span_id' => NULL,
        'name' => 'database.request_summary',
        'category' => 'database',
        '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.
    }
  }

}
