<?php

declare(strict_types=1);

namespace Drupal\native_observability_execution\Service;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Guzzle middleware that records outbound HTTP spans.
 *
 * This captures:
 * - method
 * - host
 * - path (no query string)
 * - status code
 * - duration.
 */
final readonly class GuzzleTraceMiddleware {

  public function __construct(
    private ExecutionSpanRecorderInterface $recorder,
  ) {
  }

  /**
   * Returns a callable middleware compatible with Guzzle HandlerStack::push().
   */
  public function __invoke(callable $handler): callable {
    return function (RequestInterface $request, array $options) use ($handler) {
      $start = microtime(TRUE);

      return $handler($request, $options)->then(
        function (ResponseInterface $response) use ($request, $start) {
          $this->record($request, $start, (int) $response->getStatusCode(), NULL);
          return $response;
        },
        function ($reason) use ($request, $start) {
          $this->record($request, $start, NULL, $reason);
          throw $reason;
        }
      );
    };
  }

  /**
   * Records an outbound HTTP interaction as an execution span.
   *
   * @param \Psr\Http\Message\RequestInterface $request
   *   The outbound request.
   * @param float $start
   *   The microtime start timestamp.
   * @param int|null $status
   *   The HTTP status code when available.
   * @param mixed $reason
   *   The failure reason when the request was rejected.
   */
  private function record(RequestInterface $request, float $start, ?int $status, $reason): void {
    $duration_ms = (int) round((microtime(TRUE) - $start) * 1000);

    $uri = $request->getUri();

    // Http / https.
    $scheme = $uri->getScheme();
    $host = $uri->getHost();
    $port = $uri->getPort();
    $path = $uri->getPath() ?: '/';
    $query = $uri->getQuery();

    $method = strtoupper($request->getMethod());

    // Build full URL including scheme and optional port.
    $base = $scheme . '://' . $host;

    // Include port only if non-standard.
    if ($port !== NULL && !in_array($port, [80, 443], TRUE)) {
      $base .= ':' . $port;
    }

    $fullUrl = $base . $path;

    if ($query !== '') {
      $fullUrl .= '?' . $query;
    }

    // Name stays compact (for table readability).
    $name = sprintf('%s %s', $method, $host);

    $meta = [
      'method' => $method,
      'host' => $host,
    // Now contains full URL.
      'path' => $fullUrl,
    ];

    if ($status !== NULL) {
      $meta['status'] = $status;
      $meta['outcome'] = 'ok';
    }
    else {
      $meta['outcome'] = 'error';
      $meta['error_type'] = is_object($reason) ? get_class($reason) : gettype($reason);
    }

    $this->recorder->record('external.http', $name, $duration_ms, $meta);
  }

}
