<?php

declare(strict_types=1);

namespace Drupal\flowdrop_runtime\Service\Logging;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\Session\AccountInterface;
use Drupal\flowdrop_runtime\DTO\LogVerbosity;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
use Psr\Log\LogLevel;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Logger channel decorator that honors the FlowDrop logging settings.
 *
 * Every runtime service logs to the "flowdrop_runtime" channel, but most do so
 * with direct $logger->info()/debug() calls that never consulted the verbosity
 * or "log to watchdog" settings — those were only respected by ExecutionLogger.
 * This wrapper sits in front of the real channel and drops any record that the
 * central configuration says should not be written, so the settings at
 * /admin/flowdrop/config/flowdrop apply to all runtime logging uniformly.
 *
 * Filtering rules (config: flowdrop.settings → logging):
 * - log_to_watchdog = FALSE: nothing is written.
 * - verbosity = silent: nothing is written.
 * - verbosity = minimal: errors and more severe only.
 * - verbosity = standard/verbose: everything except debug-level records.
 * - verbosity = debug: everything.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
final class FilteredLoggerChannel implements LoggerChannelInterface {

  use LoggerTrait;

  /**
   * Constructs a FilteredLoggerChannel.
   *
   * @param \Drupal\Core\Logger\LoggerChannelInterface $inner
   *   The real logger channel being decorated.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The config factory used to read the FlowDrop logging settings.
   */
  public function __construct(
    private readonly LoggerChannelInterface $inner,
    private readonly ConfigFactoryInterface $configFactory,
  ) {}

  /**
   * {@inheritdoc}
   */
  public function log($level, string|\Stringable $message, array $context = []): void {
    if ($this->shouldWrite($level)) {
      $this->inner->log($level, $message, $context);
    }
  }

  /**
   * Determines whether a record at the given level should be written.
   *
   * @param int|string $level
   *   The log level, as an RFC 5424 integer or a PSR-3 level string.
   *
   * @return bool
   *   TRUE if the configuration allows this record to be logged.
   */
  private function shouldWrite(int|string $level): bool {
    $config = $this->configFactory->get('flowdrop.settings');

    // The watchdog toggle is an absolute switch: when off, suppress everything.
    if (($config->get('logging.log_to_watchdog') ?? TRUE) === FALSE) {
      return FALSE;
    }

    $verbosity = LogVerbosity::fromString((string) ($config->get('logging.verbosity') ?? 'standard'));
    $threshold = self::verbosityThreshold($verbosity);

    // Silent: nothing passes.
    if ($threshold === NULL) {
      return FALSE;
    }

    // RFC 5424 severities are lower-is-more-severe, so a record passes when its
    // severity is at least as severe as (numerically <=) the threshold.
    return self::severity($level) <= $threshold;
  }

  /**
   * Maps a verbosity level to the least-severe RFC 5424 severity it permits.
   *
   * @param \Drupal\flowdrop_runtime\DTO\LogVerbosity $verbosity
   *   The configured verbosity level.
   *
   * @return int|null
   *   The maximum (least severe) RFC 5424 level that may be logged, or NULL
   *   when nothing should be logged at all.
   */
  private static function verbosityThreshold(LogVerbosity $verbosity): ?int {
    return match ($verbosity) {
      LogVerbosity::Silent => NULL,
      LogVerbosity::Minimal => RfcLogLevel::ERROR,
      LogVerbosity::Standard, LogVerbosity::Verbose => RfcLogLevel::INFO,
      LogVerbosity::Debug => RfcLogLevel::DEBUG,
    };
  }

  /**
   * Normalizes a PSR-3 level string or RFC 5424 integer to an RFC 5424 integer.
   *
   * @param int|string $level
   *   The log level.
   *
   * @return int
   *   The RFC 5424 severity (0 = emergency … 7 = debug).
   */
  private static function severity(int|string $level): int {
    if (is_int($level)) {
      return $level;
    }

    return match ($level) {
      LogLevel::EMERGENCY => RfcLogLevel::EMERGENCY,
      LogLevel::ALERT => RfcLogLevel::ALERT,
      LogLevel::CRITICAL => RfcLogLevel::CRITICAL,
      LogLevel::ERROR => RfcLogLevel::ERROR,
      LogLevel::WARNING => RfcLogLevel::WARNING,
      LogLevel::NOTICE => RfcLogLevel::NOTICE,
      LogLevel::INFO => RfcLogLevel::INFO,
      LogLevel::DEBUG => RfcLogLevel::DEBUG,
      default => RfcLogLevel::INFO,
    };
  }

  /**
   * {@inheritdoc}
   */
  public function setRequestStack(?RequestStack $requestStack = NULL) {
    $this->inner->setRequestStack($requestStack);
  }

  /**
   * {@inheritdoc}
   */
  public function setCurrentUser(?AccountInterface $current_user = NULL) {
    $this->inner->setCurrentUser($current_user);
  }

  /**
   * {@inheritdoc}
   */
  public function setLoggers(array $loggers) {
    $this->inner->setLoggers($loggers);
  }

  /**
   * {@inheritdoc}
   */
  public function addLogger(LoggerInterface $logger, $priority = 0) {
    $this->inner->addLogger($logger, $priority);
  }

}
