<?php

declare(strict_types=1);

namespace Drupal\Tests\native_observability_execution\Unit\Service;

use Drupal\Tests\UnitTestCase;
use Drupal\native_observability_execution\Service\ExecutionSpanRecorderInterface;
use Drupal\native_observability_execution\Service\GuzzleTraceMiddleware;
use GuzzleHttp\Promise\Create;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;

/**
 * Covers outbound HTTP tracing middleware behavior.
 *
 * The purpose of these tests is to validate the normalized payload that gets
 * passed to the recorder. This is the load-bearing contract used later by the
 * execution dashboard and reporting layers.
 */
final class GuzzleTraceMiddlewareTest extends UnitTestCase {

  /**
   * Verifies that successful HTTP calls are recorded with normalized metadata.
   */
  public function testInvokeRecordsSuccessfulOutboundRequest(): void {
    $recorder = $this->createMock(ExecutionSpanRecorderInterface::class);
    $middleware = new GuzzleTraceMiddleware($recorder);

    $recorder->expects($this->once())
      ->method('record')
      ->with(
        'external.http',
        'GET api.example.com',
        $this->greaterThanOrEqual(0),
        $this->callback(static function (array $meta): bool {
          return $meta['method'] === 'GET'
            && $meta['host'] === 'api.example.com'
            && $meta['path'] === 'https://api.example.com/v1/ping?verbose=1'
            && $meta['status'] === 200
            && $meta['outcome'] === 'ok';
        })
      );

    $handler = static fn($request, array $options) => Create::promiseFor(new Response(200));
    $callable = $middleware($handler);

    $callable(new Request('GET', 'https://api.example.com/v1/ping?verbose=1'), [])->wait();
  }

  /**
   * Verifies that failed HTTP calls are recorded with an error outcome.
   */
  public function testInvokeRecordsFailedOutboundRequest(): void {
    $recorder = $this->createMock(ExecutionSpanRecorderInterface::class);
    $middleware = new GuzzleTraceMiddleware($recorder);

    $recorder->expects($this->once())
      ->method('record')
      ->with(
        'external.http',
        'POST api.example.com',
        $this->greaterThanOrEqual(0),
        $this->callback(static function (array $meta): bool {
          return $meta['method'] === 'POST'
            && $meta['host'] === 'api.example.com'
            && $meta['path'] === 'https://api.example.com/submit'
            && $meta['outcome'] === 'error'
            && isset($meta['error_type']);
        })
      );

    $handler = static fn($request, array $options) => Create::rejectionFor(new \RuntimeException('Remote failure'));
    $callable = $middleware($handler);

    try {
      $callable(new Request('POST', 'https://api.example.com/submit'), [])->wait();
      $this->fail('The promise rejection was expected to be re-thrown by the middleware.');
    }
    catch (\RuntimeException $exception) {
      $this->assertSame('Remote failure', $exception->getMessage());
    }
  }

}
