// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/log.go in the NATS server Go source.

using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;

namespace ZB.MOM.NatsNet.Server.Internal;

/// <summary>
/// NATS server Logger interface.
/// Mirrors the Go <c>Logger</c> interface in log.go.
/// In .NET we bridge to <see cref="ILogger"/> from Microsoft.Extensions.Logging.
/// </summary>
public interface INatsLogger
{
    void Noticef(string format, params object[] args);
    void Warnf(string format, params object[] args);
    void Fatalf(string format, params object[] args);
    void Errorf(string format, params object[] args);
    void Debugf(string format, params object[] args);
    void Tracef(string format, params object[] args);
}

/// <summary>
/// Server logging state. Encapsulates the logger, debug/trace flags, and rate-limiting.
/// Mirrors the logging fields of Go's <c>Server</c> struct (logging struct + rateLimitLogging sync.Map).
/// </summary>
public sealed class ServerLogging
{
    private readonly object _lock = new();
    private INatsLogger? _logger;
    private int _debug;
    private int _trace;
    private int _traceSysAcc;
    private readonly ConcurrentDictionary<string, DateTime> _rateLimitMap = new();

    /// <summary>Gets the current logger (thread-safe).</summary>
    public INatsLogger? GetLogger()
    {
        lock (_lock) return _logger;
    }

    /// <summary>
    /// Sets the logger with debug/trace flags.
    /// Mirrors <c>Server.SetLoggerV2</c>.
    /// </summary>
    public void SetLoggerV2(INatsLogger? logger, bool debugFlag, bool traceFlag, bool sysTrace)
    {
        Interlocked.Exchange(ref _debug, debugFlag ? 1 : 0);
        Interlocked.Exchange(ref _trace, traceFlag ? 1 : 0);
        Interlocked.Exchange(ref _traceSysAcc, sysTrace ? 1 : 0);

        lock (_lock)
        {
            if (_logger is IDisposable disposable)
                disposable.Dispose();
            _logger = logger;
        }
    }

    /// <summary>
    /// Sets the logger. Mirrors <c>Server.SetLogger</c>.
    /// </summary>
    public void SetLogger(INatsLogger? logger, bool debugFlag, bool traceFlag) =>
        SetLoggerV2(logger, debugFlag, traceFlag, false);

    public bool IsDebug => Interlocked.CompareExchange(ref _debug, 0, 0) != 0;
    public bool IsTrace => Interlocked.CompareExchange(ref _trace, 0, 0) != 0;
    public bool IsTraceSysAcc => Interlocked.CompareExchange(ref _traceSysAcc, 0, 0) != 0;

    /// <summary>Executes a log call under the read lock.</summary>
    public void ExecuteLogCall(Action<INatsLogger> action)
    {
        INatsLogger? logger;
        lock (_lock) logger = _logger;
        if (logger == null) return;
        action(logger);
    }

    // ---- Convenience methods ----

    public void Noticef(string format, params object[] args) =>
        ExecuteLogCall(l => l.Noticef(format, args));

    public void Errorf(string format, params object[] args) =>
        ExecuteLogCall(l => l.Errorf(format, args));

    public void Errors(object scope, Exception e) =>
        ExecuteLogCall(l => l.Errorf("{0} - {1}", scope, e.Message));

    public void Errorc(string ctx, Exception e) =>
        ExecuteLogCall(l => l.Errorf("{0}: {1}", ctx, e.Message));

    public void Errorsc(object scope, string ctx, Exception e) =>
        ExecuteLogCall(l => l.Errorf("{0} - {1}: {2}", scope, ctx, e.Message));

    public void Warnf(string format, params object[] args) =>
        ExecuteLogCall(l => l.Warnf(format, args));

    public void Fatalf(string format, params object[] args) =>
        ExecuteLogCall(l => l.Fatalf(format, args));

    public void Debugf(string format, params object[] args)
    {
        if (!IsDebug) return;
        ExecuteLogCall(l => l.Debugf(format, args));
    }

    public void Tracef(string format, params object[] args)
    {
        if (!IsTrace) return;
        ExecuteLogCall(l => l.Tracef(format, args));
    }

    /// <summary>
    /// Rate-limited warning log. Only the first occurrence of each formatted statement is logged.
    /// Mirrors <c>Server.RateLimitWarnf</c>.
    /// </summary>
    public void RateLimitWarnf(string format, params object[] args)
    {
        var statement = string.Format(format, args);
        if (!_rateLimitMap.TryAdd(statement, DateTime.UtcNow)) return;
        Warnf("{0}", statement);
    }

    /// <summary>
    /// Rate-limited debug log. Only the first occurrence of each formatted statement is logged.
    /// Mirrors <c>Server.RateLimitDebugf</c>.
    /// </summary>
    public void RateLimitDebugf(string format, params object[] args)
    {
        var statement = string.Format(format, args);
        if (!_rateLimitMap.TryAdd(statement, DateTime.UtcNow)) return;
        Debugf("{0}", statement);
    }

    /// <summary>
    /// Rate-limited format warning. Only the first occurrence of each format string is logged.
    /// Mirrors <c>Server.rateLimitFormatWarnf</c>.
    /// </summary>
    internal void RateLimitFormatWarnf(string format, params object[] args)
    {
        if (!_rateLimitMap.TryAdd(format, DateTime.UtcNow)) return;
        var statement = string.Format(format, args);
        Warnf("{0}", statement);
    }

    // ---- Trace sanitization ----
    // Mirrors removeSecretsFromTrace / redact in server/client.go.
    // passPat  = `"?\s*pass\S*?"?\s*[:=]\s*"?(([^",\r\n}])*)` — captures the value of any pass/password field.
    // tokenPat = `"?\s*auth_token\S*?"?\s*[:=]\s*"?(([^",\r\n}])*)` — captures auth_token value.
    // Only the FIRST match is redacted (mirrors the Go break-after-first-match behaviour).

    // Go: "?\s*pass\S*?"?\s*[:=]\s*"?(([^",\r\n}])*)
    private static readonly Regex s_passPattern = new(
        @"""?\s*pass\S*?""?\s*[:=]\s*""?(([^"",\r\n}])*)",
        RegexOptions.Compiled);

    // Go: "?\s*auth_token\S*?"?\s*[:=]\s*"?(([^",\r\n}])*)
    private static readonly Regex s_authTokenPattern = new(
        @"""?\s*auth_token\S*?""?\s*[:=]\s*""?(([^"",\r\n}])*)",
        RegexOptions.Compiled);

    /// <summary>
    /// Removes passwords from a protocol trace string.
    /// Mirrors <c>removeSecretsFromTrace</c> in client.go (pass step).
    /// Only the first occurrence is redacted.
    /// </summary>
    public static string RemovePassFromTrace(string s)
        => RedactFirst(s_passPattern, s);

    /// <summary>
    /// Removes auth_token from a protocol trace string.
    /// Mirrors <c>removeSecretsFromTrace</c> in client.go (auth_token step).
    /// Only the first occurrence is redacted.
    /// </summary>
    public static string RemoveAuthTokenFromTrace(string s)
        => RedactFirst(s_authTokenPattern, s);

    /// <summary>
    /// Removes both passwords and auth tokens from a protocol trace string.
    /// Mirrors <c>removeSecretsFromTrace</c> in client.go.
    /// </summary>
    public static string RemoveSecretsFromTrace(string s)
        => RemoveAuthTokenFromTrace(RemovePassFromTrace(s));

    private static string RedactFirst(Regex pattern, string s)
    {
        var m = pattern.Match(s);
        if (!m.Success) return s;
        var cap = m.Groups[1]; // captured value substring
        return string.Concat(s.AsSpan(0, cap.Index), "[REDACTED]", s.AsSpan(cap.Index + cap.Length));
    }
}

/// <summary>
/// Adapter that bridges <see cref="INatsLogger"/> to <see cref="ILogger"/>.
/// </summary>
public sealed class MicrosoftLoggerAdapter : INatsLogger
{
    private readonly ILogger _logger;

    public MicrosoftLoggerAdapter(ILogger logger) => _logger = logger;

    internal ILogger UnderlyingLogger => _logger;

    public void Noticef(string format, params object[] args) =>
        _logger.LogInformation(format, args);

    public void Warnf(string format, params object[] args) =>
        _logger.LogWarning(format, args);

    public void Fatalf(string format, params object[] args) =>
        _logger.LogCritical(format, args);

    public void Errorf(string format, params object[] args) =>
        _logger.LogError(format, args);

    public void Debugf(string format, params object[] args) =>
        _logger.LogDebug(format, args);

    public void Tracef(string format, params object[] args) =>
        _logger.LogTrace(format, args);
}
