#nullable enable

using MMoneyCoderSharp.Data;
using MBN_STOCK_WEBVIEW.Infrastructure;
using MySqlConnector;
using Oracle.ManagedDataAccess.Client;

namespace MBN_STOCK_WEBVIEW.DbWriteSmoke;

/// <summary>
/// Emits only a provider/type classification. Messages, SQL, credentials,
/// identifiers, and values deliberately never cross this boundary.
/// </summary>
internal static class RedactedDatabaseFailureClassifier
{
    internal static string Classify(Exception exception)
    {
        ArgumentNullException.ThrowIfNull(exception);
        foreach (var current in Flatten(exception))
        {
            switch (current)
            {
                case OracleException oracle:
                    return "Oracle-" + oracle.Number.ToString(
                        System.Globalization.CultureInfo.InvariantCulture);
                case MySqlException mariaDb:
                    return "MariaDb-" + ((int)mariaDb.ErrorCode).ToString(
                        System.Globalization.CultureInfo.InvariantCulture);
                case DatabaseOperationException database:
                    return database.DataSource + "-" +
                        (database.ProviderErrorCode?.ToString(
                            System.Globalization.CultureInfo.InvariantCulture) ?? "none");
                case ManualFinancialMutationRejectedException rejected:
                    return "ManualRejected-" + rejected.Reason;
                case OperatorCatalogConflictException conflict:
                    return "CatalogConflict-" + conflict.Kind + "-" + conflict.CommandKind;
            }
        }

        return exception.GetType().Name;
    }

    private static IEnumerable<Exception> Flatten(Exception exception)
    {
        var pending = new Stack<Exception>();
        var visited = new HashSet<Exception>(ReferenceEqualityComparer.Instance);
        pending.Push(exception);
        while (pending.TryPop(out var current) && visited.Add(current))
        {
            yield return current;
            if (current is AggregateException aggregate)
            {
                foreach (var inner in aggregate.InnerExceptions.Reverse())
                {
                    pending.Push(inner);
                }
            }
            else if (current.InnerException is not null)
            {
                pending.Push(current.InnerException);
            }
        }
    }
}
