<?php

declare(strict_types=1);

namespace Drupal\native_observability_database_observer\EventSubscriber;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Database;
use Drupal\native_observability\EventSubscriber\TraceSubscriber;
use Drupal\native_observability\Service\RequestLogExclusionMatcher;
use Drupal\native_observability_database_observer\Service\DatabaseObserverStorage;
use Drupal\native_observability_database_observer\Service\DatabaseQueryClassifier;
use Drupal\native_observability_database_observer\Service\DatabaseQueryRuntimeCollector;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Captures lightweight database query metrics during the request lifecycle.
 *
 * This subscriber keeps per-request counting lightweight by doing all normal
 * aggregation in memory and persisting only slow queries in the RAW table.
 */
final class DatabaseQueryObserverSubscriber implements EventSubscriberInterface {

  private const LOG_KEY = 'native_observability_database_observer';

  /**
   * Creates a new database query observer subscriber instance.
   */
  public function __construct(
    private readonly ConfigFactoryInterface $configFactory,
    private readonly DatabaseObserverStorage $storage,
    private readonly DatabaseQueryClassifier $classifier,
    private readonly DatabaseQueryRuntimeCollector $runtimeCollector,
  ) {
  }

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

  /**
   * Starts the in-memory database log for the current main request.
   */
  public function onKernelRequest(RequestEvent $event): void {
    if (!$event->isMainRequest() || !$this->isCaptureEnabled()) {
      return;
    }

    if (RequestLogExclusionMatcher::isRequestExcluded($event->getRequest())) {
      return;
    }

    $this->runtimeCollector->reset('http');

    // Ensure log starts clean.
    Database::startLog(self::LOG_KEY);
  }

  /**
   * Computes the request summary and persists slow queries above the threshold.
   */
  public function onKernelResponse(ResponseEvent $event): void {
    if (!$event->isMainRequest() || !$this->isCaptureEnabled()) {
      return;
    }

    if (RequestLogExclusionMatcher::isRequestExcluded($event->getRequest())) {
      return;
    }

    $request = $event->getRequest();
    $requestId = (string) ($request->attributes->get(TraceSubscriber::ATTR_REQUEST_ID) ?? '');
    $routeName = (string) ($request->attributes->get('_route') ?? '');
    $method = $request->getMethod();
    $path = $request->getPathInfo();

    $threshold = (float) ($this->getConfig()->get('slow_query_threshold_ms') ?? 100);
    $storeQueryText = (bool) ($this->getConfig()->get('store_slow_query_text') ?? TRUE);
    $maxStoredQueries = (int) ($this->getConfig()->get('max_stored_queries_per_request') ?? 20);

    $queries = Database::getLog(self::LOG_KEY) ?: [];
    $slowRows = [];

    foreach ($queries as $query) {
      $durationMs = (float) (($query['time'] ?? 0) * 1000);
      $isSlow = $durationMs >= $threshold;

      $this->runtimeCollector->recordQuery($durationMs, $isSlow);

      if (!$isSlow) {
        continue;
      }

      $queryText = (string) ($query['query'] ?? '');
      $slowRows[] = [
        'request_id' => $requestId !== '' ? $requestId : NULL,
        'trace_id' => NULL,
        'route_name' => $routeName !== '' ? $routeName : NULL,
        'method' => $method,
        'path' => $path,
        'relation_type' => $this->classifier->classify($requestId, $routeName),
        'connection_key' => (string) ($query['key'] ?? 'default'),
        'connection_target' => (string) ($query['target'] ?? 'default'),
        'duration_ms' => round($durationMs, 3),
        'is_slow' => 1,
        'query_hash' => $this->classifier->buildQueryHash($queryText),
        'query_text' => $storeQueryText ? $queryText : NULL,
        'caller' => isset($query['caller']) && is_string($query['caller']) ? $query['caller'] : NULL,
        'created_at' => time(),
      ];
    }

    $summary = $this->runtimeCollector->getRequestSummary();
    $storedRows = 0;

    foreach (array_slice($slowRows, 0, max(0, $maxStoredQueries)) as $row) {
      $this->storage->insert($row + [
        'payload' => [
          'context_type' => 'http',
          'time_seconds' => round(((float) $row['duration_ms']) / 1000, 6),
          'request_summary' => $summary + [
            'slow_threshold_ms' => $threshold,
          ],
        ],
      ]);
      $storedRows++;
    }

    $request->attributes->set('native_observability.database.summary', $summary + [
      'stored_slow_queries' => $storedRows,
      'slow_threshold_ms' => $threshold,
    ]);
  }

  /**
   * Returns whether capture is enabled in configuration.
   */
  private function isCaptureEnabled(): bool {
    return (bool) ($this->getConfig()->get('capture_enabled') ?? TRUE);
  }

  /**
   * Returns the immutable runtime configuration object for this observer.
   */
  private function getConfig() {
    return $this->configFactory->get('native_observability_database_observer.settings');
  }

}
