<?php

declare(strict_types=1);

namespace Drupal\Tests\native_observability\Unit\EventSubscriber;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\native_observability\EventSubscriber\TraceSubscriber;
use Drupal\native_observability\Service\RequestIdGeneratorInterface;
use Drupal\native_observability\Service\TraceStorageInterface;
use Psr\Log\LoggerInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Unit tests for TraceSubscriber::isOwnRouteExcluded().
 *
 * IsOwnRouteExcluded() is private; it is invoked via ReflectionMethod. The
 * TraceSubscriber constructor requires six collaborators so all non-config
 * dependencies receive no-op mocks.
 *
 * The method delegates to getSettingBool('capture.exclude_own_routes', TRUE)
 * which reads from the cached getSettings() array. Tests reset the internal
 * $settings cache via reflection so each assertion reads fresh config.
 */
#[CoversClass(TraceSubscriber::class)]
final class TraceSubscriberRouteExclusionTest extends UnitTestCase {

  /**
   * Builds a TraceSubscriber with a given exclude_own_routes config value.
   *
   * @param bool|null $excludeValue
   *   The value of capture.exclude_own_routes, or NULL when the key is absent.
   *
   * @return \Drupal\native_observability\EventSubscriber\TraceSubscriber
   *   Subscriber instance ready for reflection-based assertions.
   */
  private function buildSubscriber(bool|null $excludeValue): TraceSubscriber {
    $captureArray = $excludeValue !== NULL
      ? ['exclude_own_routes' => $excludeValue]
      : [];

    $config = $this->createMock(ImmutableConfig::class);
    $config->method('get')->willReturnMap([
      ['capture', $captureArray],
      ['retention', []],
    ]);

    $factory = $this->createMock(ConfigFactoryInterface::class);
    $factory->method('get')
      ->with('native_observability.settings')
      ->willReturn($config);

    return new TraceSubscriber(
      $this->createMock(RequestIdGeneratorInterface::class),
      $this->createMock(TraceStorageInterface::class),
      $this->createMock(LoggerInterface::class),
      $this->createMock(AccountProxyInterface::class),
      $this->createMock(RequestStack::class),
      $factory,
    );
  }

  /**
   * Invokes the private isOwnRouteExcluded() method via reflection.
   *
   * @param \Drupal\native_observability\EventSubscriber\TraceSubscriber $subscriber
   *   The subscriber instance.
   * @param string $routeName
   *   The route name to check.
   *
   * @return bool
   *   The method return value.
   */
  private function callIsOwnRouteExcluded(
    TraceSubscriber $subscriber,
    string $routeName,
  ): bool {
    $method = new \ReflectionMethod(TraceSubscriber::class, 'isOwnRouteExcluded');
    return (bool) $method->invoke($subscriber, $routeName);
  }

  /**
   * Verifies TRUE when the flag is enabled and the route prefix matches.
   */
  public function testReturnsTrueWhenFlagEnabledAndPrefixMatches(): void {
    $subscriber = $this->buildSubscriber(TRUE);

    $this->assertTrue(
      $this->callIsOwnRouteExcluded($subscriber, 'native_observability.dashboard'),
    );
  }

  /**
   * Verifies TRUE for the Prometheus scrape route when the flag is enabled.
   */
  public function testReturnsTrueForPrometheusScrapeRoute(): void {
    $subscriber = $this->buildSubscriber(TRUE);

    $this->assertTrue(
      $this->callIsOwnRouteExcluded($subscriber, 'native_observability_otel.prometheus_scrape'),
    );
  }

  /**
   * Verifies FALSE when the flag is enabled but the prefix does not match.
   */
  public function testReturnsFalseWhenFlagEnabledAndPrefixDoesNotMatch(): void {
    $subscriber = $this->buildSubscriber(TRUE);

    $this->assertFalse(
      $this->callIsOwnRouteExcluded($subscriber, 'node.view'),
    );
  }

  /**
   * Verifies FALSE when the flag is explicitly disabled regardless of prefix.
   */
  public function testReturnsFalseWhenFlagDisabled(): void {
    $subscriber = $this->buildSubscriber(FALSE);

    $this->assertFalse(
      $this->callIsOwnRouteExcluded($subscriber, 'native_observability.dashboard'),
    );
  }

  /**
   * Verifies the safe default of TRUE when the config key is absent.
   *
   * GetSettingBool() falls back to the provided default (TRUE) when the key
   * is not present in the settings array, so new installs automatically
   * exclude own routes without requiring an explicit config:import.
   */
  public function testDefaultsTrueWhenKeyAbsent(): void {
    $subscriber = $this->buildSubscriber(NULL);

    $this->assertTrue(
      $this->callIsOwnRouteExcluded($subscriber, 'native_observability.dashboard'),
    );
  }

}
