using System.Text.RegularExpressions;
using ZB.MOM.WW.Audit;
using ZB.MOM.WW.ScadaBridge.AuditLog.Payload;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
using static ZB.MOM.WW.ScadaBridge.AuditLog.Payload.AuditRedactionPrimitives;

namespace ZB.MOM.WW.ScadaBridge.AuditLog.Redaction;

/// <summary>
/// Minimal always-safe <see cref="IAuditRedactor"/> fallback for composition
/// roots that bypass the full <see cref="ScadaBridgeAuditRedactor"/>.
/// Performs line-oriented HTTP header
/// redaction for the always-sensitive defaults (Authorization, X-Api-Key,
/// Cookie, Set-Cookie) on the <c>RequestSummary</c> / <c>ResponseSummary</c>
/// fields carried inside <c>ZB.MOM.WW.Audit.AuditEvent.DetailsJson</c>. Does NOT
/// perform body-regex redaction, SQL-parameter redaction, or truncation — those
/// need <see cref="ScadaBridgeAuditRedactor"/> with live options. Contract:
/// over-redact safely, never throw, never miss a header on the default
/// sensitive list.
/// </summary>
public sealed class SafeDefaultAuditRedactor : IAuditRedactor
{
    /// <summary>Singleton instance — the redactor is stateless and side-effect-free.</summary>
    public static SafeDefaultAuditRedactor Instance { get; } = new SafeDefaultAuditRedactor();

    private static readonly string[] DefaultHeaderRedactList =
    {
        "Authorization",
        "X-Api-Key",
        "Cookie",
        "Set-Cookie",
    };

    private static readonly Regex HeaderRegex = new(
        @"(?<name>[A-Za-z][A-Za-z0-9\-_]*)\s*:\s*(?<value>[^\r\n]*)",
        RegexOptions.Compiled | RegexOptions.IgnoreCase);

    private SafeDefaultAuditRedactor() { }

    /// <summary>
    /// Applies line-oriented header redaction to the default sensitive headers
    /// (<c>Authorization</c>, <c>X-Api-Key</c>, <c>Cookie</c>, <c>Set-Cookie</c>)
    /// found in <c>RequestSummary</c> and <c>ResponseSummary</c> inside
    /// <paramref name="rawEvent"/>.<c>DetailsJson</c>. Never throws; over-redacts on
    /// any internal failure.
    /// </summary>
    /// <param name="rawEvent">The audit event whose details JSON is to be redacted.</param>
    /// <returns>A new <see cref="AuditEvent"/> with sensitive headers replaced by the redacted marker, or an over-redacted sentinel on failure.</returns>
    public AuditEvent Apply(AuditEvent rawEvent)
    {
        ArgumentNullException.ThrowIfNull(rawEvent);

        // Fast path: no DetailsJson means no summaries to scrub.
        if (string.IsNullOrEmpty(rawEvent.DetailsJson))
        {
            return rawEvent;
        }

        try
        {
            var d = AuditDetailsCodec.Deserialize(rawEvent.DetailsJson);
            var scrubbed = d with
            {
                RequestSummary = RedactHeaders(d.RequestSummary),
                ResponseSummary = RedactHeaders(d.ResponseSummary),
            };
            return rawEvent with { DetailsJson = AuditDetailsCodec.Serialize(scrubbed) };
        }
        catch
        {
            // Over-redact: suppress ALL sensitive free-text fields so a failure
            // on any internal path never leaks the original. The contract is
            // "never throw." Uses the shared OverRedactedEventMarker so all
            // redactor safety-nets emit the same sentinel string.
            var safe = new AuditDetails
            {
                RequestSummary = OverRedactedEventMarker,
                ResponseSummary = OverRedactedEventMarker,
                ErrorDetail = OverRedactedEventMarker,
                ErrorMessage = OverRedactedEventMarker,
                Extra = OverRedactedEventMarker,
                PayloadTruncated = true,
            };
            return rawEvent with { DetailsJson = AuditDetailsCodec.Serialize(safe) };
        }
    }

    private static string? RedactHeaders(string? summary)
    {
        if (string.IsNullOrEmpty(summary)) return summary;

        return HeaderRegex.Replace(summary, m =>
        {
            var name = m.Groups["name"].Value;
            foreach (var sensitive in DefaultHeaderRedactList)
            {
                if (string.Equals(name, sensitive, StringComparison.OrdinalIgnoreCase))
                {
                    // Use the shared RedactedMarker so line-format and JSON-format
                    // header redaction emit the same sentinel string.
                    return $"{name}: {RedactedMarker}";
                }
            }
            return m.Value;
        });
    }
}
