<?php

namespace Sibs\Stargate\Loggers;

use Sibs\Stargate\Constants\Logs;
use Sibs\Stargate\Utils\SibsObjectify;

defined('ABSPATH') || exit;

class SibsLogger
{
    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

    /**
     * Woocommerce Logger.
     *
     * @var \WC_Logger_Interface
     */
    private static $logger;

    /**
     * Logger ID.
     *
     * @var string
     */
    private static $logId;

    /**
     * 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
     */
    public static function debug($message, array $context = []): void
    {
        self::addRecord(self::LEVEL_DEBUG, $message, $context, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Info Log.
     *
     * @param mixed $message
     */
    public static function info($message, array $context = []): void
    {
        self::addRecord(self::LEVEL_INFO, $message, $context, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Notice Log.
     *
     * @param mixed $message
     */
    public static function notice($message, array $context = []): void
    {
        self::addRecord(self::LEVEL_NOTICE, $message, $context, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Warning Log.
     *
     * @param mixed $message
     */
    public static function warning($message, array $context = []): void
    {
        self::addRecord(self::LEVEL_WARNING, $message, $context, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Error Log.
     *
     * @param mixed $message
     */
    public static function error($message, array $context = []): void
    {
        self::addRecord(self::LEVEL_ERROR, $message, $context, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Critical Log.
     *
     * @param mixed $message
     */
    public static function critical($message, array $context = []): void
    {
        self::addRecord(self::LEVEL_CRITICAL, $message, $context, self::LOG_CAPSULATE_DEPTH);
    }

    /**
     * Emergency Log.
     *
     * @param mixed $message
     */
    public static function emergency($message, array $context = []): void
    {
        self::addRecord(self::LEVEL_EMERGENCY, $message, $context, 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 array  $context         Context of the 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, array $context, int $traceDepth = self::LOG_UNCAPSULATE_DEPTH): void
    {
        if (!array_key_exists('source', $context)) {
            $context['source'] = Logs::DEFAULT;
        }

        $prettyMessage = self::isArrayOrJson($message) ? self::prettify($message) : $message;
        $messageToLog = self::formatMessageToLog($prettyMessage, $traceDepth);

        self::getInstance()->{$sibsLoggerLevel}($messageToLog, $context);
    }

    /**
     * Prettify Message to be logged.
     *
     * @param mixed $message
     */
    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
     */
    protected static function isArrayOrJson($message): bool
    {
        if (is_array($message)) {
            return true;
        }

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

        json_decode($message);

        return JSON_ERROR_NONE === json_last_error();
    }

    /**
     * @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'].'::' : '')
            .($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.
     */
    protected static function getLogId(): string
    {
        if (is_null(self::$logId)) {
            self::$logId = uniqid();
        }

        return self::$logId;
    }
}
