<?php

declare(strict_types=1);

namespace Drupal\native_observability_metrics\Service;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Database\Connection;

/**
 * Aggregates route performance metrics from observability traces.
 */
final class RouteMetricsAggregator {

  /**
   * Constructor.
   */
  public function __construct(
    private readonly Connection $database,
    private readonly PercentileCalculator $percentileCalculator,
    private readonly TimeInterface $time,
    private readonly DatabaseMetricsStorage $metricsStorage,
  ) {
  }

  /**
   * Aggregates the latest completed 5-minute window and stores the result.
   */
  public function aggregateLatestCompletedWindow(): array {
    $now = $this->time->getCurrentTime();
    $window_end = (int) (floor($now / 300) * 300);
    $window_start = $window_end - 300;

    $metrics = $this->aggregateWindow($window_start, $window_end);
    $this->metricsStorage->replaceWindowMetrics($window_start, $window_end, $metrics);

    return $metrics;
  }

  /**
   * Aggregates route metrics for a specific window.
   */
  public function aggregateWindow(int $window_start, int $window_end): array {
    $query = $this->database->select('native_observability_trace', 't')
      ->fields('t', [
        'route_name',
        'path',
        'duration_ms',
        'started_at',
      ])
      ->condition('t.started_at', $window_start, '>=')
      ->condition('t.started_at', $window_end, '<')
      ->isNotNull('t.duration_ms')
      ->condition('t.route_name', ['system.css_asset', 'system.js_asset'], 'NOT IN');

    $results = $query->execute()->fetchAll();

    if (!$results) {
      return [];
    }

    $grouped = [];

    foreach ($results as $row) {
      $route_name = isset($row->route_name) ? (string) $row->route_name : '';
      $path = isset($row->path) ? (string) $row->path : '';
      $duration = (float) $row->duration_ms;

      $route_key = $route_name !== '' ? $route_name : $path;
      if ($route_key === '') {
        $route_key = 'unknown';
      }

      if (!isset($grouped[$route_key])) {
        $grouped[$route_key] = [
          'route_key' => $route_key,
          'route_name' => $route_name,
          'durations' => [],
        ];
      }

      $grouped[$route_key]['durations'][] = $duration;
    }

    $metrics = [];

    foreach ($grouped as $item) {
      $durations = $item['durations'];
      $count = count($durations);

      if ($count === 0) {
        continue;
      }

      $avg = array_sum($durations) / $count;

      $metrics[] = [
        'route_key' => $item['route_key'],
        'route_name' => $item['route_name'] !== '' ? $item['route_name'] : $item['route_key'],
        'request_count' => $count,
        'throughput_per_minute' => round($count / 5, 2),
        'avg_duration_ms' => (int) round($avg),
        'p95_duration_ms' => (int) round($this->percentileCalculator->calculate($durations, 95)),
        'p99_duration_ms' => (int) round($this->percentileCalculator->calculate($durations, 99)),
        'window_start' => $window_start,
        'window_end' => $window_end,
      ];
    }

    usort(
      $metrics,
      static fn(array $a, array $b): int => $b['request_count'] <=> $a['request_count']
    );

    return $metrics;
  }

  /**
   * Returns the latest persisted snapshot.
   */
  public function getLatestSnapshot(): array {
    return $this->metricsStorage->getLatestSnapshot();
  }

  /**
   * Aggregates the most recent rolling time window and stores the result.
   *
   * @param int $seconds
   *   Rolling window size in seconds.
   *
   * @return array
   *   Aggregated metric rows.
   */
  public function aggregateRollingWindow(int $seconds = 300): array {
    $window_end = $this->time->getCurrentTime();
    $window_start = $window_end - $seconds;

    $metrics = $this->aggregateWindow($window_start, $window_end);
    $this->metricsStorage->replaceWindowMetrics($window_start, $window_end, $metrics);

    return $metrics;
  }

}
