#nullable enable

using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.Infrastructure;

/// <summary>
/// Identifies the native database-write boundary that is requesting
/// authorization. The request deliberately contains no operator-entered data.
/// </summary>
public enum DatabaseMutationTarget
{
    OperatorCatalog,
    NamedPlaylist,
    ManualFinancial
}

/// <summary>
/// Describes one validated transaction immediately before its executor can
/// create a database connection.
/// </summary>
public sealed record DatabaseMutationAuthorizationRequest(
    DatabaseMutationTarget Target,
    DataSourceKind Source,
    Guid OperationId,
    string MutationKind);

/// <summary>
/// Final authorization seam for state-changing database operations. Returning
/// false denies the transaction before a connection or transaction is created.
/// Throwing also fails closed and is never converted into an allow decision.
/// </summary>
public interface IDatabaseMutationAuthorization
{
    bool IsAuthorized(DatabaseMutationAuthorizationRequest request);
}

/// <summary>
/// Provides the compatibility policy used when existing constructors do not
/// supply an explicit database-mutation authorization policy.
/// </summary>
public static class DatabaseMutationAuthorization
{
    public static IDatabaseMutationAuthorization AllowAll { get; } =
        new AllowAllDatabaseMutationAuthorization();

    private sealed class AllowAllDatabaseMutationAuthorization
        : IDatabaseMutationAuthorization
    {
        public bool IsAuthorized(DatabaseMutationAuthorizationRequest request)
        {
            ArgumentNullException.ThrowIfNull(request);
            return true;
        }
    }
}

/// <summary>
/// Raised when the active policy denies a database mutation before any native
/// database connection is created.
/// </summary>
public sealed class DatabaseMutationAuthorizationException : InvalidOperationException
{
    public DatabaseMutationAuthorizationException(
        DatabaseMutationAuthorizationRequest request,
        Exception? innerException = null)
        : base(
            $"The {request?.Target} database mutation was denied before a connection was opened.",
            innerException)
    {
        Request = request ?? throw new ArgumentNullException(nameof(request));
    }

    public DatabaseMutationAuthorizationRequest Request { get; }
}

internal static class DatabaseMutationAuthorizationGuard
{
    internal static void Demand(
        IDatabaseMutationAuthorization authorization,
        DatabaseMutationAuthorizationRequest request)
    {
        ArgumentNullException.ThrowIfNull(authorization);
        ArgumentNullException.ThrowIfNull(request);
        bool authorized;
        try
        {
            authorized = authorization.IsAuthorized(request);
        }
        catch (Exception exception)
        {
            throw new DatabaseMutationAuthorizationException(request, exception);
        }

        if (!authorized)
        {
            throw new DatabaseMutationAuthorizationException(request);
        }
    }
}
