<?php

declare(strict_types=1);

namespace Drupal\native_observability_dashboard\Service;

use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\native_observability_dashboard\ValueObject\TimeRange;

/**
 * Builds the primary KPI section for the dashboard.
 */
final class PrimaryKpiSectionBuilder {

  use StringTranslationTrait;

  /**
   * Creates a new primary KPI section builder instance.
   *
   * @param \Drupal\native_observability_dashboard\Service\DashboardAvailabilityResolver $availabilityResolver
   *   The dashboard availability resolver service.
   * @param \Drupal\native_observability_dashboard\Service\TraceMetricsReader $traceMetricsReader
   *   The trace metrics reader service.
   * @param \Drupal\native_observability_dashboard\Service\MetricValueFormatter $metricValueFormatter
   *   The metric value formatter service.
   * @param \Drupal\native_observability_dashboard\Service\SpanBreakdownSectionBuilder $spanBreakdownSectionBuilder
   *   The span breakdown section builder for the Response Time card donut.
   */
  public function __construct(
    private DashboardAvailabilityResolver $availabilityResolver,
    private TraceMetricsReader $traceMetricsReader,
    private MetricValueFormatter $metricValueFormatter,
    private SpanBreakdownSectionBuilder $spanBreakdownSectionBuilder,
  ) {
  }

  /**
   * Builds the primary KPI section.
   *
   * @param \Drupal\native_observability_dashboard\ValueObject\TimeRange|null $range
   *   The selected dashboard range, or NULL to use the default one.
   *
   * @return array
   *   The section render payload.
   */
  public function build(?TimeRange $range = NULL): array {
    $range ??= TimeRange::createDefault();

    if (!$this->availabilityResolver->hasTraceSupport()) {
      return [
        'enabled' => FALSE,
        'title' => 'Primary KPIs',
        'time_range' => $range->getLabel(),
        'reason' => 'The trace storage layer is not currently available.',
        'cards' => [],
      ];
    }

    $totalTraces = $this->traceMetricsReader->getTraceCount($range);
    $ajaxTraces = $this->traceMetricsReader->getAjaxTraceCount($range);
    $totalErrors = $this->traceMetricsReader->getErrorTraceCount($range);
    $ajaxErrors = $this->traceMetricsReader->getAjaxErrorTraceCount($range);
    $avgDuration = $this->traceMetricsReader->getAverageDuration($range);
    $p95Duration = $this->traceMetricsReader->getP95Duration($range);
    $requestsPerSecond = $this->traceMetricsReader->getRequestsPerSecond($range);

    $ajaxShare = $totalTraces > 0 ? round(($ajaxTraces / $totalTraces) * 100, 2) : 0.0;
    $errorRate = $totalTraces > 0 ? round(($totalErrors / $totalTraces) * 100, 2) : 0.0;
    $ajaxErrorRate = $ajaxTraces > 0 ? round(($ajaxErrors / $ajaxTraces) * 100, 2) : 0.0;

    $spanBreakdown = $this->buildResponseTimeBreakdown($range);
    $trafficSeries = $this->traceMetricsReader->getTraceCountSeries($range);
    $peakBucket = $this->extractPeakBucket($trafficSeries);
    $lastActivity = $this->extractLastActivityLabel($trafficSeries);
    $mostActiveChannel = $ajaxTraces > 0 && $ajaxShare >= 50 ? 'AJAX' : 'HTTP';

    $avgDurationFormatted = $this->metricValueFormatter->formatDuration($avgDuration);
    $p95DurationFormatted = $this->metricValueFormatter->formatDuration($p95Duration);
    $ajaxShareFormatted = $this->metricValueFormatter->formatPercentage($ajaxShare);
    $errorRateFormatted = $this->metricValueFormatter->formatPercentage($errorRate);
    $ajaxErrorRateFormatted = $this->metricValueFormatter->formatPercentage($ajaxErrorRate);

    return [
      'enabled' => TRUE,
      'title' => 'Primary KPIs',
      'time_range' => $range->getLabel(),
      'cards' => [
        [
          'id' => 'traffic_overview',
          'title' => 'Traffic Overview',
          'icon' => 'activity',
          'color' => 'blue',
          'description' => 'Total requests in the selected time range.',
          'primary' => [
            'value' => number_format($totalTraces),
            'label' => 'Requests',
          ],
          'secondary' => [
            'value' => number_format($ajaxTraces),
            'label' => 'AJAX Requests',
          ],
          'meta' => [
            [
              'label' => 'AJAX Share',
              'value' => $ajaxShareFormatted['formatted_value'] . $ajaxShareFormatted['unit'],
            ],
            [
              'label' => 'Requests/sec',
              'value' => number_format($requestsPerSecond, 2),
            ],
          ],
          'timeseries' => $trafficSeries,
          'chart_id' => 'traffic-overview-chart',
        ],
        [
          'id' => 'response_time',
          'title' => 'Response Time',
          'icon' => 'clock',
          'color' => 'orange',
          'description' => 'Average request duration for the selected time range.',
          'primary' => [
            'value' => $avgDurationFormatted['formatted_value'] . $avgDurationFormatted['unit'],
            'label' => 'Average',
          ],
          'secondary' => [
            'value' => $p95DurationFormatted['formatted_value'] . $p95DurationFormatted['unit'],
            'label' => 'P95 Latency',
          ],
          'meta' => [
            [
              'label' => 'Trace Count',
              'value' => number_format($totalTraces),
            ],
          ],
          'timeseries' => $this->traceMetricsReader->getAverageDurationSeries($range),
          'chart_id' => 'response-time-chart',
          'breakdown' => $spanBreakdown,
        ],
        [
          'id' => 'reliability',
          'title' => 'Reliability',
          'icon' => 'check',
          'color' => 'green',
          'description' => 'Percentage of failed requests for the selected time range.',
          'primary' => [
            'value' => $errorRateFormatted['formatted_value'] . $errorRateFormatted['unit'],
            'label' => 'Error Rate',
          ],
          'secondary' => [
            'value' => $ajaxErrorRateFormatted['formatted_value'] . $ajaxErrorRateFormatted['unit'],
            'label' => 'AJAX Error Rate',
          ],
          'meta' => [
            [
              'label' => 'Failures',
              'value' => number_format($totalErrors),
            ],
            [
              'label' => 'AJAX Failures',
              'value' => number_format($ajaxErrors),
            ],
          ],
          'timeseries' => $this->traceMetricsReader->getErrorRateSeries($range),
          'chart_id' => 'reliability-chart',
        ],
        [
          'id' => 'runtime_signal',
          'title' => 'Runtime Signal',
          'icon' => 'alert',
          'color' => 'red',
          'description' => 'Peak traffic snapshot and latest runtime activity.',
          'primary' => [
            'value' => $peakBucket['value'],
            'label' => 'Peak Bucket',
          ],
          'secondary' => [
            'value' => $peakBucket['label'],
            'label' => 'Peak Window',
          ],
          'meta' => [
            [
              'label' => 'Last Activity',
              'value' => $lastActivity,
            ],
            [
              'label' => 'Most Active Channel',
              'value' => $mostActiveChannel,
            ],
          ],
          'timeseries' => $trafficSeries,
          'chart_id' => 'runtime-signal-chart',
        ],
      ],
    ];
  }

  /**
   * Builds the breakdown structure for the Response Time card donut.
   *
   * Wraps SpanBreakdownSectionBuilder output into the card-level breakdown
   * payload consumed by the KPI card template and the JS donut init.
   *
   * @param \Drupal\native_observability_dashboard\ValueObject\TimeRange $range
   *   The selected time range.
   *
   * @return array
   *   Breakdown payload with enabled, chart_id, total_ms, series, summary.
   */
  private function buildResponseTimeBreakdown(TimeRange $range): array {
    $colors = [
      'database' => '#f97316',
      'http.client' => '#3b82f6',
      'drupal' => '#8b5cf6',
      'ajax' => '#10b981',
      'http' => '#0ea5e9',
    ];

    $section = $this->spanBreakdownSectionBuilder->build($range);

    if (empty($section['enabled'])) {
      return ['enabled' => FALSE];
    }

    $series = [];
    $summary = [];

    $total = array_sum(array_column($section['breakdown'], 'avg_duration_ms'));

    $descriptions = [
      'database' => (string) $this->t('Time spent on DB queries'),
      'http.client' => (string) $this->t('Time on outbound API calls'),
      'drupal' => (string) $this->t('PHP execution, rendering, cache'),
      'ajax' => (string) $this->t('Avg AJAX request duration'),
      'http' => (string) $this->t('Avg non-AJAX request time'),
    ];

    foreach ($section['breakdown'] as $item) {
      $category = (string) ($item['category'] ?? '');
      $label = (string) ($item['label'] ?? $category);
      $value = (float) ($item['avg_duration_ms'] ?? 0);
      $color = $colors[$category] ?? '#94a3b8';
      $pct = $total > 0 ? round(($value / $total) * 100) : 0;

      $series[] = [
        'name' => $label,
        'value' => $value,
        'description' => $descriptions[$category] ?? $label,
        'itemStyle' => ['color' => $color],
      ];

      $summary[] = [
        'label' => $label,
        'value' => $pct . '%',
        'color' => $color,
      ];
    }

    return [
      'enabled' => TRUE,
      'chart_id' => 'response-time-breakdown-donut',
      'total_ms' => (float) ($section['trace_avg_ms'] ?? 0),
      'series' => $series,
      'summary' => $summary,
    ];
  }

  /**
   * Extracts peak bucket information from a count series.
   *
   * @param array $series
   *   The count series.
   *
   * @return array
   *   The peak bucket info.
   */
  private function extractPeakBucket(array $series): array {
    $peak = [
      'label' => '—',
      'value' => '0',
    ];

    foreach ($series as $point) {
      $value = (int) ($point['value'] ?? 0);
      if ($value > (int) $peak['value']) {
        $peak = [
          'label' => (string) ($point['label'] ?? '—'),
          'value' => number_format($value),
        ];
      }
    }

    return $peak;
  }

  /**
   * Extracts the last activity label from the provided series.
   *
   * @param array $series
   *   The metric series.
   *
   * @return string
   *   The last non-empty label.
   */
  private function extractLastActivityLabel(array $series): string {
    $last = '—';

    foreach (array_reverse($series) as $point) {
      if ((int) ($point['value'] ?? 0) > 0) {
        $last = (string) ($point['label'] ?? '—');
        break;
      }
    }

    return $last;
  }

}
