<?php

declare(strict_types=1);

namespace Drupal\native_observability_otel\Service;

/**
 * Builds Elastic-compatible telemetry documents from stored traces.
 *
 * Output format:
 * - NDJSON (one JSON document per line)
 * - ECS-inspired structure compatible with Elastic pipelines.
 */
final class ElasticTelemetryBuilder {

  /**
   * Builds a single Elastic-compatible document from a trace row.
   *
   * @param array|object $trace
   *   Raw trace row from storage.
   *
   * @return array
   *   ECS-like telemetry document.
   */
  public function buildDocument(array|object $trace): array {
    $trace = is_object($trace) ? (array) $trace : $trace;

    $payload = [];
    if (!empty($trace['payload']) && is_string($trace['payload'])) {
      $decoded = json_decode($trace['payload'], TRUE);
      if (is_array($decoded)) {
        $payload = $decoded;
      }
    }

    $started_at = (int) ($trace['started_at'] ?? time());
    $duration_ms = isset($trace['duration_ms']) ? (int) $trace['duration_ms'] : NULL;
    $status_code = isset($trace['status_code']) ? (int) $trace['status_code'] : NULL;
    $uid = isset($trace['uid']) ? (int) $trace['uid'] : NULL;

    return [
      '@timestamp' => gmdate('c', $started_at),
      'message' => $this->buildMessage($trace),
      'event' => [
        'kind' => 'event',
        'category' => ['web'],
        'type' => ['access'],
        'outcome' => $this->resolveOutcome($status_code),
        'duration' => $duration_ms !== NULL ? $duration_ms * 1000000 : NULL,
      ],
      'http' => [
        'request' => [
          'method' => (string) ($trace['method'] ?? ''),
        ],
        'response' => [
          'status_code' => $status_code,
        ],
      ],
      'url' => [
        'path' => (string) ($trace['path'] ?? ''),
      ],
      'user' => [
        'id' => $uid ?: NULL,
      ],
      'client' => [
        'ip' => (string) ($trace['client_ip'] ?? ''),
      ],
      'trace' => [
        'id' => (string) ($trace['request_id'] ?? ''),
      ],
      'labels' => [
        'drupal.route_name' => (string) ($trace['route_name'] ?? ''),
        'drupal.trace_id' => isset($trace['id']) ? (string) $trace['id'] : '',
        'drupal.module' => 'native_observability_otel',
      ],
      'drupal' => [
        'native_observability_otel' => [
          'trace_id' => isset($trace['id']) ? (int) $trace['id'] : NULL,
          'request_id' => (string) ($trace['request_id'] ?? ''),
          'started_at' => $started_at,
          'ended_at' => isset($trace['ended_at']) ? (int) $trace['ended_at'] : NULL,
          'duration_ms' => $duration_ms,
          'route_name' => (string) ($trace['route_name'] ?? ''),
          'exception' => $payload['exception'] ?? NULL,
        ],
      ],
    ];
  }

  /**
   * Builds NDJSON output from trace rows.
   *
   * @param array $traces
   *   Trace rows.
   *
   * @return string
   *   NDJSON payload.
   */
  public function buildNdjson(array $traces): string {
    $lines = [];

    foreach ($traces as $trace) {
      $doc = $this->buildDocument($trace);
      $lines[] = json_encode($doc, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    }

    return implode("\n", $lines) . "\n";
  }

  /**
   * Builds message summary.
   *
   * @param array $trace
   *   Normalized trace row.
   *
   * @return string
   *   Message string.
   */
  private function buildMessage(array $trace): string {
    $method = (string) ($trace['method'] ?? 'UNKNOWN');
    $path = (string) ($trace['path'] ?? '/');
    $status = isset($trace['status_code']) ? (string) $trace['status_code'] : '-';

    return sprintf('%s %s -> %s', $method, $path, $status);
  }

  /**
   * Resolves ECS event outcome.
   *
   * @param int|null $status_code
   *   HTTP status code.
   *
   * @return string|null
   *   success, failure, or unknown.
   */
  private function resolveOutcome(?int $status_code): ?string {
    if ($status_code === NULL) {
      return 'unknown';
    }

    if ($status_code < 400) {
      return 'success';
    }

    return 'failure';
  }

}
