<?php

namespace XcVm\Core\Logging;

/**
 * Файловый логгер
 *
 * Записывает бизнес-события в файл в формате base64-encoded JSON.
 * Каждая строка — отдельная запись. Файл блокируется при записи (LOCK_EX).
 *
 * Формат записи:
 *   base64({"type":"pdo","message":"...","extra":"...","line":0,"time":1708300000,"env":"cli"})
 *
 * Фильтрация:
 *   - Игнорируются записи, содержащие 'panel_logs' в extra (рекурсивные логи)
 *   - Игнорируются 'timeout exceeded', 'lock wait timeout', 'duplicate entry' (шумные ошибки MySQL)
 *
 * @see LoggerInterface
 *
 * @package XC_VM_Core_Logging
 * @author  Divarion_D <https://github.com/Divarion-D>
 * @copyright 2025-2026 Vateron Media
 * @link    https://github.com/Vateron-Media/XC_VM
 * @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html
 */

require_once __DIR__ . '/LoggerInterface.php';

class FileLogger implements LoggerInterface {
    /**
     * Путь к файлу лога.
     * По умолчанию используется LOGS_TMP_PATH . 'error_log.log'
     *
     * @var string|null
     */
    private static ?string $logFile = null;

    /**
     * Установить путь к файлу лога.
     *
     * @param string $path Полный путь к файлу
     */
    public static function setLogFile(string $path): void {
        self::$logFile = $path;
    }

    /**
     * Получить текущий путь к файлу лога.
     * Если не установлен явно, используется LOGS_TMP_PATH . 'error_log.log'
     *
     * @return string
     */
    public static function getLogFile(): string {
        if (self::$logFile !== null) {
            return self::$logFile;
        }

        if (defined('LOGS_TMP_PATH')) {
            return LOGS_TMP_PATH . 'error_log.log';
        }

        return '/tmp/xc_vm_error.log';
    }

    /**
     * Записать лог-сообщение в файл.
     *
     * Перед записью проверяется фильтр: шумные ошибки MySQL и рекурсивные
     * обращения к panel_logs игнорируются.
     *
     * @param string     $type    Тип события ('pdo', 'epg', 'error', и т.д.)
     * @param string     $message Текст сообщения
     * @param string|int $extra   Дополнительные данные (SQL-запрос, trace, и т.д.)
     * @param int        $line    Номер строки (опционально)
     */
    public static function log(string $type, string $message, $extra = '', int $line = 0): void {
        $extra = (string) $extra;

        $rTrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
        $rCaller = $rTrace[1] ?? [];
        $rFile = (string) ($rCaller['file'] ?? '');
        if ($line <= 0 && isset($rCaller['line'])) {
            $line = (int) $rCaller['line'];
        }

        // Фильтрация шумных / рекурсивных записей
        if (self::shouldSkip($message, $extra)) {
            return;
        }

        $rData = [
            'type'    => $type,
            'message' => $message,
            'extra'   => $extra,
            'file'    => $rFile,
            'line'    => $line,
            'time'    => time(),
            'env'     => php_sapi_name(),
        ];

        $logLine = base64_encode(json_encode($rData, JSON_UNESCAPED_UNICODE)) . "\n";

        file_put_contents(
            self::getLogFile(),
            $logLine,
            FILE_APPEND | LOCK_EX
        );
    }

    /**
     * Проверить, нужно ли пропустить запись (фильтрация шума).
     *
     * @param string $message Текст сообщения
     * @param string $extra   Дополнительные данные
     *
     * @return bool true — пропустить, false — записать
     */
    private static function shouldSkip(string $message, string $extra): bool {
        // Рекурсивный лог: запись о таблице panel_logs
        if (stripos($extra, 'panel_logs') !== false) {
            return true;
        }

        // Шумные ошибки MySQL — пропускаем
        $noisy = ['timeout exceeded', 'lock wait timeout', 'duplicate entry'];
        $messageLower = strtolower($message);
        foreach ($noisy as $pattern) {
            if (strpos($messageLower, $pattern) !== false) {
                return true;
            }
        }

        return false;
    }
}
