using System.Text.Json;

namespace ROLAC.API.Services.Logging;

/// <summary>
/// Serializes audit before/after payloads to JSON and masks sensitive property names.
/// Shared by <see cref="AuditLogger"/> and the EF audit interceptor so masking is consistent.
/// </summary>
public static class AuditChangeSerializer
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
    };

    /// <summary>Property names whose values are replaced with <see cref="MaskValue"/> wherever they appear.</summary>
    private static readonly HashSet<string> SensitiveNames = new(StringComparer.OrdinalIgnoreCase)
    {
        "BankAccountNumber",
        "BankRoutingNumber",
        "PasswordHash",
        "Password",
        "SecurityStamp",
        "ConcurrencyStamp",
    };

    public const string MaskValue = "***";

    public static bool IsSensitive(string propertyName) => SensitiveNames.Contains(propertyName);

    /// <summary>Builds the <c>{ before, after }</c> JSON; returns null when both sides are empty.</summary>
    public static string? BuildChanges(object? before, object? after)
    {
        if (before is null && after is null)
            return null;

        var payload = new Dictionary<string, object?>();
        if (before is not null) payload["before"] = MaskObject(before);
        if (after  is not null) payload["after"]  = MaskObject(after);

        return JsonSerializer.Serialize(payload, JsonOptions);
    }

    /// <summary>Serializes a value (e.g. a property dictionary built by the interceptor) to JSON.</summary>
    public static string Serialize(object value) => JsonSerializer.Serialize(value, JsonOptions);

    /// <summary>
    /// Masks a free-form object by reflecting over its public properties. Used for the explicit
    /// IAuditLogger.Write path (the interceptor masks per-property as it builds its dictionary).
    /// </summary>
    private static object MaskObject(object value)
    {
        if (value is IDictionary<string, object?> dict)
        {
            var masked = new Dictionary<string, object?>();
            foreach (var (key, val) in dict)
                masked[key] = IsSensitive(key) ? MaskValue : val;
            return masked;
        }

        var result = new Dictionary<string, object?>();
        foreach (var prop in value.GetType().GetProperties())
        {
            if (!prop.CanRead || prop.GetIndexParameters().Length > 0)
                continue;
            result[prop.Name] = IsSensitive(prop.Name) ? MaskValue : prop.GetValue(value);
        }
        return result;
    }
}
