<?php

/**
 * Log data to the WooCommerce logger and DataDog.
 *
 * @package northmill-online-shared
 */
declare (strict_types=1);
namespace Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules;

use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Loggers\Abstract_Logger;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Loggers\Monolog_Stream_Logger;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Loggers\WC\WC_Extended_Logger;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Loggers\WooCommerce_Logger;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Abstract_Shared_Base;
defined('ABSPATH') || exit;
/**
 * Class Logger
 */
class Logger_Module extends Abstract_Module
{
    const LOG_HANDLED_KEY = 'northmill-online-shared-handled-log';
    const WC_CLASS_VERSION_CACHE_KEY = 'northmill_online_shared_wc_logging_class';
    /**
     * Log level stored in settings.
     * Is set in Shared→Setup(), looking at the settings.
     *
     * It defaults to debug before the setting is read.
     *
     * @var Log_Level_Enum
     */
    private Log_Level_Enum $settings_log_level = Log_Level_Enum::DEBUG;
    /**
     * Array of logger handlers.
     *
     * @var Abstract_Logger[]
     */
    private array $logger_handlers;
    /**
     * Class constructor.
     *
     * @param Abstract_Shared_Base $shared Shared instance.
     *
     * @return void
     */
    public function __construct(Abstract_Shared_Base $shared)
    {
        parent::__construct($shared);
        $this->logger_handlers = array('woocommerce' => new WooCommerce_Logger($this->shared), 'monolog_stream' => new Monolog_Stream_Logger($this->shared, enabled: \false));
        if ($shared->nm_hosting) {
            // Defaults to only handle entries of level warning or higher if NM_CUSTOM_MONOLOG_LEVEL isn't set.
            $this->logger_handlers['monolog_stream']->set_enabled(\true);
            $this->shared->globals->add_unique_filter('woocommerce_logging_class', array($this, 'wc_logging_class'), 10, 0);
        }
    }
    /**
     * Setup the logger.
     *
     * @return void
     */
    public function setup(): void
    {
        $debug_logging_enabled = $this->shared->admin->settings->is_setting_enabled('debug_log_enabled');
        $log_level = $debug_logging_enabled ? Log_Level_Enum::DEBUG : self::get_default_log_level();
        $this->set_settings_log_level($log_level);
    }
    /**
     * Set the log level for the settings.
     *
     * @param Log_Level_Enum $settings_log_level The log level to be set for the settings.
     *
     * @return void
     */
    public function set_settings_log_level(Log_Level_Enum $settings_log_level): void
    {
        $this->settings_log_level = $settings_log_level;
    }
    /**
     * Enables a logger handler.
     *
     * @param string $handler_key The key to identify the handler.
     *
     * @return void
     */
    public function enable_handler(string $handler_key): void
    {
        if (!$this->handler_exists($handler_key)) {
            return;
        }
        $this->logger_handlers[$handler_key]->set_enabled(\true);
    }
    /**
     * Disables a logger handler.
     *
     * @param string $handler_key The key of the handler to disable.
     *
     * @return void
     */
    public function disable_handler(string $handler_key): void
    {
        if (!$this->handler_exists($handler_key)) {
            return;
        }
        $this->logger_handlers[$handler_key]->set_enabled(\false);
    }
    /**
     * Determines whether a specific logger handler is enabled or not.
     *
     * @param string $handler_key The key of the handler to check.
     *
     * @return bool Returns true if the handler is enabled, false otherwise.
     */
    public function is_handler_enabled(string $handler_key): bool
    {
        return $this->handler_exists($handler_key) && $this->logger_handlers[$handler_key]->is_enabled();
    }
    /**
     * Check if a handler exists.
     *
     * @param string $handler_key The key of the handler to check.
     *
     * @return bool True if the handler exists, false otherwise.
     */
    public function handler_exists(string $handler_key): bool
    {
        if (!isset($this->logger_handlers[$handler_key])) {
            $this->log('logger', 'Handler not found', array('handler_key' => $handler_key), Log_Level_Enum::ERROR);
            return \false;
        }
        return \true;
    }
    /**
     * Add a log entry.
     *
     * @param string         $channel The channel to log to.
     * @param string         $message The message to be logged.
     * @param null|array     $context The data to be logged.
     * @param Log_Level_Enum $level   Log level.
     * @param bool           $external_log Whether the log is external (not from our package) or not.
     */
    public function log(string $channel, string $message, ?array $context = null, Log_Level_Enum $level = Log_Level_Enum::INFO, bool $external_log = \false): void
    {
        if (!$level || !$this->is_log_level_enabled($level)) {
            return;
        }
        if (is_null($context)) {
            $context = array();
        }
        foreach ($this->logger_handlers as $handler) {
            if ($handler->is_enabled()) {
                $handler->log($channel, $message, $context, $level, $external_log);
            }
        }
    }
    /**
     * Get the enabled log level stored in settings.
     * Can also be used by plugins to get the log level.
     *
     * @return Log_Level_Enum
     */
    private function get_enabled_log_level(): Log_Level_Enum
    {
        return $this->settings_log_level;
    }
    /**
     * Get all enabled log levels.
     *
     * @return array
     */
    private function get_enabled_levels(): array
    {
        $enabled_level = $this->get_enabled_log_level();
        if (Log_Level_Enum::DISABLED === $enabled_level) {
            return array();
        }
        $levels = Log_Level_Enum::cases();
        // Get the numeric index of the search key.
        $index = array_search($enabled_level, $levels, \true);
        // Slice from 0 up to index + 1. Plus 1 to include the search key.
        return array_slice($levels, 0, $index + 1);
    }
    /**
     * Check if a log level is enabled.
     *
     * @param Log_Level_Enum $level Log level to check.
     *
     * @return bool True if enabled, false otherwise.
     */
    private function is_log_level_enabled(Log_Level_Enum $level): bool
    {
        $enabled_levels = $this->get_enabled_levels();
        return in_array($level, $enabled_levels, \true);
    }
    /**
     * Get the default log level.
     * Can also be used by plugins. For example, for the plugin's settings page.
     *
     * @return Log_Level_Enum
     */
    public static function get_default_log_level(): Log_Level_Enum
    {
        return Log_Level_Enum::INFO;
    }
    /**
     * Get the WooCommerce logging class.
     *
     * @return string
     */
    public function wc_logging_class(): string
    {
        return WC_Extended_Logger::class;
    }
    /**
     * Get a WooCommerce log file URL.
     *
     * @param bool $ignore_handler_status Whether to ignore the handler status.
     *
     * @return string
     */
    public function get_wc_logs_url(bool $ignore_handler_status = \false): string
    {
        if (defined('WC_LOG_DIR') && ($ignore_handler_status || $this->is_handler_enabled('woocommerce'))) {
            return add_query_arg(array('page' => 'wc-status', 'tab' => 'logs'), admin_url('admin.php'));
        }
        return '';
    }
    /**
     * Get a link tag to the WooCommerce log file.
     *
     * @param string|null $text   Text to display for the link.
     *
     * @return string Link HTML tag.
     * @noinspection PhpUnused Used by plugins loading this composer package.
     */
    public function get_wc_logs_a_tag(?string $text = null): string
    {
        $url = self::get_wc_logs_url();
        if (empty($url)) {
            return '';
        }
        $text = $text ?? __('View log', 'northmill-online-shared');
        return '<a href="' . esc_url($url) . '" target="_blank">' . esc_html($text) . '</a>';
    }
}
