<?php

declare(strict_types=1);

namespace Drupal\native_observability\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Url;
use Drupal\native_observability\Service\NativeObservabilityUiBuilder;
use Drupal\native_observability\Service\TraceStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Displays details for a single trace record.
 *
 * This page shows:
 * - Core metadata (method, path, route, status, duration, timestamps,
 *   and client IP)
 * - Stored JSON payload (pretty-printed when possible)
 *
 * IMPORTANT:
 * - Do not place Url objects inside '#attributes'. Twig Attribute will
 *   fail on object values.
 */
final class TraceViewController extends ControllerBase {

  /**
   * Constructs the controller.
   *
   * @param \Drupal\native_observability\Service\TraceStorageInterface $storage
   *   Trace storage service.
   * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
   *   Date formatter service.
   * @param \Drupal\native_observability\Service\NativeObservabilityUiBuilder $ui
   *   UI builder service (safe render arrays for links/buttons).
   */
  public function __construct(
    private readonly TraceStorageInterface $storage,
    private readonly DateFormatterInterface $date_formatter,
    private readonly NativeObservabilityUiBuilder $ui,
  ) {
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): self {
    return new self(
      $container->get('native_observability.trace_storage'),
      $container->get('date.formatter'),
      $container->get('native_observability.ui_builder'),
    );
  }

  /**
   * Detail page callback.
   *
   * @param int $id
   *   Trace ID.
   *
   * @return array
   *   Render array.
   */
  public function view(int $id): array {
    $trace = $this->storage->load($id);
    if (!$trace) {
      throw new NotFoundHttpException('Trace not found.');
    }

    $payload_raw = (string) ($trace['payload'] ?? '');
    $payload_pretty = $this->prettyPrintJson($payload_raw);

    // Extract parent request id (AJAX correlation) from JSON payload.
    $parent_request_id = NULL;
    $decoded = json_decode($payload_raw, TRUE);
    if (is_array($decoded) && !empty($decoded['parent_request_id'])) {
      $parent_request_id = (string) $decoded['parent_request_id'];
    }

    $items = [
      $this->t('Request ID: @id', ['@id' => (string) ($trace['request_id'] ?? '')]),
      $this->t('Method: @m', ['@m' => (string) ($trace['method'] ?? '')]),
      $this->t('Path: @p', ['@p' => (string) ($trace['path'] ?? '')]),
      $this->t('Route: @r', ['@r' => (string) ($trace['route_name'] ?? '-')]),
      $this->t('Status code: @s', ['@s' => (string) ($trace['status_code'] ?? '-')]),
      $this->t('Duration (ms): @d', ['@d' => (string) ($trace['duration_ms'] ?? '-')]),
      $this->t('Started: @t', [
        '@t' => $this->date_formatter->format((int) ($trace['started_at'] ?? time()), 'short'),
      ]),
      $this->t('Ended: @t', [
        '@t' => !empty($trace['ended_at'])
          ? $this->date_formatter->format((int) $trace['ended_at'], 'short')
          : '-',
      ]),
      $this->t('User ID: @u', ['@u' => (string) ($trace['uid'] ?? '-')]),
      $this->t('Client IP: @ip', [
        '@ip' => (string) ($trace['client_ip'] ?? '-'),
      ]),
    ];

    $build = [
      '#type' => 'container',

      // Back action kept as a safe render array via the UI builder.
      // Implement ui->backToTracesButton() in the UI builder if needed.
      'actions' => [
        '#type' => 'container',
        '#attributes' => ['style' => 'margin-bottom:12px; display:flex; gap:8px; flex-wrap:wrap;'],
        'back' => $this->ui->backToTracesButton(),
        // If an export route is added later, expose it through the UI
        // builder here as well.
        // 'export' => $this->ui->exportTraceJsonButton($id),
      ],

      'meta' => [
        '#theme' => 'item_list',
        '#title' => $this->t('Trace metadata'),
        '#items' => $items,
      ],
    ];

    // Add parent request link when available.
    if ($parent_request_id) {
      $build['meta_parent'] = [
        '#type' => 'container',
        '#attributes' => ['style' => 'margin: 6px 0 0;'],
        'label' => [
          '#markup' => '<strong>' . $this->t('Triggered by parent request')->render() . ':</strong> ',
        ],
        'link' => [
          '#type' => 'link',
          '#title' => $parent_request_id,
          '#url' => Url::fromRoute(
            'native_observability.trace_view_by_request_id',
            ['request_id' => $parent_request_id]
          ),
        ],
      ];
    }

    $build['payload'] = [
      '#type' => 'details',
      '#title' => $this->t('Payload (JSON)'),
      '#open' => TRUE,
      'content' => [
        '#type' => 'textarea',
        '#value' => $payload_pretty,
        '#attributes' => [
          'readonly' => 'readonly',
          'style' => 'font-family: monospace; min-height: 420px;',
        ],
      ],
    ];

    return $build;
  }

  /**
   * Detail page callback by correlation request id (ULID).
   *
   * @param string $request_id
   *   The correlation request id.
   *
   * @return array
   *   Render array.
   */
  public function viewByRequestId(string $request_id): array {
    $trace = $this->storage->loadByRequestId($request_id);
    if (!$trace) {
      throw new NotFoundHttpException('Trace not found.');
    }

    // Reuse the existing numeric trace view.
    return $this->view((int) ($trace['id'] ?? 0));
  }

  /**
   * Pretty-prints JSON when possible, otherwise returns the raw string.
   *
   * @param string $raw
   *   Raw payload string.
   *
   * @return string
   *   Pretty-printed JSON or raw string.
   */
  private function prettyPrintJson(string $raw): string {
    $raw = trim($raw);
    if ($raw === '') {
      return '';
    }

    $decoded = json_decode($raw, TRUE);
    if (!is_array($decoded)) {
      return $raw;
    }

    return json_encode(
      $decoded,
      JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
    ) ?: $raw;
  }

}
