<?php

namespace Sibs2;

defined('ABSPATH') || exit;

class sibsLogger
{
    private static $logger = null;
    // Please, don't use this variable ever. Instead use SibsLogger::getLogId().
    private static $logId = null;

    public const LOG_UNCAPSULATE_DEPTH = 2;
    public const LOG_CAPSULATE_DEPTH   = 3;

    public const LEVEL_DEBUG     = 'debug';         // 1
    public const LEVEL_INFO      = 'info';          // 2
    public const LEVEL_NOTICE    = 'notice';        // 3
    public const LEVEL_WARNING   = 'warning';       // 4
    public const LEVEL_ERROR     = 'error';         // 5
    public const LEVEL_CRITICAL  = 'critical';      // 6
    public const LEVEL_ALERT     = 'alert';         // 7
    public const LEVEL_EMERGENCY = 'emergency';     // 8

    /**
     * Constructor.
     */
    private function __construct()
    {
    }

    /**
     * Clone.
     */
    private function __clone()
    {
    }

    /**
     * Wakeup.
     */
    public function __wakeup()
    {
    }

    /**
     * Get loggger Instance.
     */
    public static function getInstance()
    {
        if (is_null(self::$logger) && function_exists('wc_get_logger')) {
            self::$logger = wc_get_logger();
        }

        return self::$logger;
    }

    /**
     * Debug Log.
     *
     * @param mixed $message
     * @param string $source
     */
    public static function debug($message, string $source = sibsConstants::SIBS_LOG): void
    {
        self::addRecord(self::LEVEL_DEBUG, $message, $source, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Info Log.
     *
     * @param mixed $message
     * @param string $source
     */
    public static function info($message, string $source = sibsConstants::SIBS_LOG): void
    {
        self::addRecord(self::LEVEL_INFO, $message, $source, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Notice Log.
     *
     * @param mixed $message
     * @param string $source
     */
    public static function notice($message, string $source = sibsConstants::SIBS_LOG): void
    {
        self::addRecord(self::LEVEL_NOTICE, $message, $source, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Warning Log.
     *
     * @param mixed $message
     * @param string $source
     */
    public static function warning($message, string $source = sibsConstants::SIBS_LOG): void
    {
        self::addRecord(self::LEVEL_WARNING, $message, $source, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Error Log.
     *
     * @param mixed $message
     * @param string $source
     */
    public static function error($message, string $source = sibsConstants::SIBS_LOG): void
    {
        self::addRecord(self::LEVEL_ERROR, $message, $source, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Critical Log.
     *
     * @param mixed $message
     * @param string $source
     */
    public static function critical($message, string $source = sibsConstants::SIBS_LOG): void
    {
        self::addRecord(self::LEVEL_CRITICAL, $message, $source, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Emergency Log.
     *
     * @param mixed $message
     * @param string $source
     */
    public static function emergency($message, string $source = sibsConstants::SIBS_LOG): void
    {
        self::addRecord(self::LEVEL_EMERGENCY, $message, $source, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Add a record to the log.
     *
     * @param string $sibsLoggerLevel Possible values are all SibsLogger::LEVEL_*
     * @param mixed $message Message to add to log
     * @param string $source Source file to log
     * @param int $traceDepth Stack trace depth of the method that called the logger
     *
     * @note: $level should be transformed into severity level of respective e-commerce.
     *        For that, use self::translateSibsLevelToPluginLevel($sibsLoggerLevel)
     * @note: $message should be formatted for a regular log message with more injected info
     *        For that, use self::formatMessageToLog($message)
     */
    public static function addRecord(string $sibsLoggerLevel, $message, string $source, int $traceDepth = self::LOG_UNCAPSULATE_DEPTH): void
    {
        $prettyMessage = self::isArrayOrJson($message) ? self::prettify($message) : $message;
        $messageToLog  = self::formatMessageToLog($prettyMessage, $traceDepth);

        self::getInstance()->{$sibsLoggerLevel}($messageToLog, ['source' => $source]);
    }

    /**
     * Prettify Message to be logged.
     *
     * @param mixed $message
     *
     * @return string
     */
    public static function prettify($message): string
    {
        $objectMessage = sibsObjectify::json_mapper($message);

        return json_encode($objectMessage, JSON_PRETTY_PRINT);
    }

    /**
     * Verify if given message is a json string.
     *
     * @param mixed $message
     *
     * @return bool
     */
    protected static function isArrayOrJson($message): bool
    {
        if (is_array($message)) {
            return true;
        }

        if (! is_string($message)) {
            return false;
        }

        json_decode($message);

        return json_last_error() === JSON_ERROR_NONE;
    }

    /**
     * @param string $message Message that will be formatted
     * @param int $traceDepth Stack trace depth of the method that called the logger
     *      The justification for the values of $traceDepth
     *      0 -> this method, self::formatMessageToLog
     *      1 -> self::addRecord
     *      2 -> the caller method
     */
    protected static function formatMessageToLog(string $message, int $traceDepth = self::LOG_UNCAPSULATE_DEPTH): string
    {
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $traceDepth + 1);
        // Get the call function where log is
        $traceLevel = $trace[$traceDepth] ?? [];
        // Get the line where log is called
        $traceLineLevel = $trace[($traceDepth > 0) ? ($traceDepth - 1) : $traceDepth] ?? [];

        return '[' . self::getLogId() . '] ' .
            (isset($traceLevel['class']) ? $traceLevel['class'] . '::' : '') .
            (isset($traceLevel['function']) ? $traceLevel['function'] : '') .
            (isset($traceLineLevel['line']) ? ' (line: ' . $traceLineLevel['line'] . ')' : '') .
            ' ' . $message;
    }

    /**
     * Generates a unique log id for each PHP process.
     *
     * The goal of such log id is to allow follow each PHP process in a log file
     * with several transactions processed by several PHP processes.
     *
     * @return string
     */
    protected static function getLogId(): string
    {
        if (is_null(self::$logId)) {
            self::$logId = uniqid();
        }

        return self::$logId;
    }
}
