<?php
if (!defined('ABSPATH')) exit;

class Calliope_SP_Logger {
    private const MAX_LOGS = 200;
    private const MAX_MESSAGE_LENGTH = 10000;

    public static function log($network, $status, $message) {
        if (!is_string($message)) {
            $message = wp_json_encode($message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        }

        $message = self::sanitize_message((string) $message);

        $logs = get_option('calliope_sp_debug_logs', []);
        if (!is_array($logs)) {
            $logs = [];
        }

        array_unshift($logs, [
            'date'    => current_time('mysql'),
            'network' => sanitize_key((string) $network),
            'status'  => sanitize_key((string) $status),
            'message' => $message,
        ]);

        $logs = array_slice($logs, 0, self::MAX_LOGS);
        update_option('calliope_sp_debug_logs', $logs, false);
    }

    /**
     * Reuse the log message sanitizer from settings callbacks and migrations.
     *
     * @param string $message Raw log message.
     * @return string
     */
    public static function sanitize_message_for_storage($message) {
        return self::sanitize_message($message);
    }

    private static function sanitize_message($message) {
        $message = wp_strip_all_tags((string) $message);
        $message = preg_replace('/[\x00-\x1F\x7F]/u', ' ', $message);
        $message = preg_replace('/\s+/u', ' ', $message);

        // Telegram requires the Bot Token inside the outbound URL. Preserve the
        // method for diagnostics while removing the token before any other rule.
        $message = preg_replace_callback(
            '#(https?://api\.telegram\.org/bot)([A-Za-z0-9_-]+(?::|%3A)[A-Za-z0-9_-]+)(/[A-Za-z0-9_]+)?#iu',
            static function ($matches) {
                return $matches[1] . '[redacted]' . ($matches[3] ?? '');
            },
            $message
        );
        $message = preg_replace_callback(
            '#\bbot([A-Za-z0-9_-]+(?::|%3A)[A-Za-z0-9_-]+)(/[A-Za-z0-9_]+)#iu',
            static function ($matches) {
                return 'bot[redacted]' . $matches[2];
            },
            $message
        );
        $message = preg_replace(
            '/(?<![A-Za-z0-9_-])\d{5,}:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/u',
            '[redacted]',
            $message
        );

        $patterns = [
            // JSON/body style secrets: access_token":"...", client_secret=..., oauth_token=...
            '/((?:access[_-]?token|refresh[_-]?token|client[_-]?secret|app[_-]?secret|consumer[_-]?secret|token[_-]?secret|oauth[_-]?token|oauth[_-]?token[_-]?secret|authorization[_-]?code|id[_-]?token|code[_-]?verifier|fb[_-]?exchange[_-]?token|password)\s*["\']?\s*[:=]\s*["\']?)([^"\',\s&}\]]+)/iu',

            // Query-string style secrets inside logged URLs or raw request strings.
            '/((?:[?&]|\b)(?:access_token|refresh_token|client_secret|app_secret|consumer_secret|token_secret|oauth_token|oauth_token_secret|id_token|fb_exchange_token|code|state|password)=)([^&\s"\']+)/iu',

            // Authorization headers, Bearer tokens, and cookie/header style secrets embedded in messages.
            '/((?:authorization\s*["\']?\s*[:=]\s*["\']?bearer\s+)|(?:bearer\s+))([A-Za-z0-9\-._~+\/]+=*)/iu',
            '/((?:authorization|proxy-authorization|cookie|set-cookie|x-api-key|api-key)\s*["\']?\s*[:=]\s*["\']?)([^"\',\s}\]]+)/iu',
        ];

        foreach ($patterns as $pattern) {
            $message = preg_replace_callback($pattern, static function ($matches) {
                return $matches[1] . '[redacted]';
            }, $message);
        }

        // Media adapters retain local paths internally for cleanup, but debug logs must
        // never persist the hosting filesystem layout.
        $message = preg_replace_callback(
            '/("(?:local_path|meta_static_path|canonical_path|logical_path)"\s*:\s*")([^"]+)(")/iu',
            static function ($matches) {
                $path = str_replace('\\', '/', (string) $matches[2]);
                return $matches[1] . basename($path) . $matches[3];
            },
            $message
        );
        $message = preg_replace_callback(
            "#(?<![:/\\\\A-Za-z0-9])/(?!/)(?:[^\\s\"'<>]+/)+[^\\s\"'<> ,}]+#u",
            static function ($matches) {
                return '[path-redacted]/' . basename((string) $matches[0]);
            },
            $message
        );
        $message = preg_replace_callback(
            "#\\b[A-Za-z]:[\\\\/](?:[^\\s\"'<>]+[\\\\/])+[^\\s\"'<> ,}]+#u",
            static function ($matches) {
                $path = str_replace('\\', '/', (string) $matches[0]);
                return '[path-redacted]/' . basename($path);
            },
            $message
        );

        if (function_exists('mb_substr')) {
            $message = mb_substr($message, 0, self::MAX_MESSAGE_LENGTH);
        } else {
            $message = substr($message, 0, self::MAX_MESSAGE_LENGTH);
        }

        return trim($message);
    }
}
