<?php

declare(strict_types=1);

namespace Drupal\native_observability_otel\Plugin\NativeObservabilityExporter;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\native_observability_otel\Service\OpenTelemetryPayloadBuilder;
use GuzzleHttp\ClientInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Exports request traces to an OTLP HTTP endpoint.
 *
 * @NativeObservabilityExporter(
 *   id = "opentelemetry",
 *   label = @Translation("OpenTelemetry exporter"),
 *   description = @Translation("Exports completed request traces to an OTLP HTTP endpoint.")
 * )
 */
final class OpenTelemetryExporter extends ExporterBase implements ContainerFactoryPluginInterface {

  /**
   * Constructs a new OpenTelemetry exporter plugin instance.
   *
   * @param array $configuration
   *   Plugin configuration.
   * @param string $plugin_id
   *   Plugin ID.
   * @param mixed $plugin_definition
   *   Plugin definition.
   * @param \Drupal\native_observability_otel\Service\OpenTelemetryPayloadBuilder $payload_builder
   *   The OpenTelemetry payload builder.
   * @param \GuzzleHttp\ClientInterface $http_client
   *   The HTTP client.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger channel factory.
   */
  public function __construct(
    array $configuration,
    string $plugin_id,
    mixed $plugin_definition,
    private readonly OpenTelemetryPayloadBuilder $payload_builder,
    private readonly ClientInterface $http_client,
    private readonly ConfigFactoryInterface $config_factory,
    LoggerChannelFactoryInterface $logger_factory,
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->logger = $logger_factory->get('native_observability_otel');
  }

  /**
   * Dedicated logger channel.
   *
   * @var \Psr\Log\LoggerInterface
   */
  private $logger;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('native_observability_otel.otel_payload_builder'),
      $container->get('http_client'),
      $container->get('config.factory'),
      $container->get('logger.factory'),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function isAvailable(): bool {
    $config = $this->config_factory->get('native_observability_otel.settings');

    return (bool) $config->get('enabled') && (string) $config->get('endpoint') !== '';
  }

  /**
   * {@inheritdoc}
   */
  public function export(array $payload): bool {
    if (!$this->isAvailable()) {
      return FALSE;
    }

    $config = $this->config_factory->get('native_observability_otel.settings');
    $endpoint = (string) $config->get('endpoint');
    $timeout = (float) ($config->get('timeout') ?: 2.0);

    $headers = [
      'Content-Type' => 'application/json',
    ] + $this->getConfiguredHeaders();

    $otel_payload = $this->payload_builder->build($payload);

    try {
      $response = $this->http_client->request('POST', $endpoint, [
        'headers' => $headers,
        'json' => $otel_payload,
        'timeout' => $timeout,
      ]);

      $status_code = $response->getStatusCode();

      if ($status_code >= 200 && $status_code < 300) {
        return TRUE;
      }

      $this->logger->warning('OpenTelemetry export failed with HTTP status code @code.', [
        '@code' => $status_code,
      ]);

      return FALSE;
    }
    catch (\Throwable $throwable) {
      $this->logger->warning('OpenTelemetry export failed: @message', [
        '@message' => $throwable->getMessage(),
      ]);

      return FALSE;
    }
  }

  /**
   * Returns configured custom HTTP headers.
   *
   * @return array<string, string>
   *   Configured headers.
   */
  private function getConfiguredHeaders(): array {
    $headers = $this->config_factory
      ->get('native_observability_otel.settings')
      ->get('headers');

    return is_array($headers) ? $headers : [];
  }

}
