using ZB.MOM.WW.MxGateway.Server.Security.Authentication;
using ZB.MOM.WW.MxGateway.Server.Security.Authorization;
using ZB.MOM.WW.MxGateway.Server.Sessions;

namespace ZB.MOM.WW.MxGateway.Tests.TestSupport;

/// <summary>
///     <see cref="IConstraintEnforcer"/> for tests that exercise the constraint
///     filtering and reply-merging code paths in
///     <c>MxAccessGatewayService.ApplyConstraintsAsync</c> and the
///     <c>BulkConstraintPlan</c> family. Callers supply predicates that decide
///     whether a given tag address or (server, item) handle is denied; recorded
///     denials are exposed for assertions.
/// </summary>
public sealed class PredicateConstraintEnforcer : IConstraintEnforcer
{
    /// <summary>Deny predicate keyed on tag address (returns true to deny).</summary>
    public Func<string, bool> DenyTag { get; init; } = _ => false;

    /// <summary>Deny predicate keyed on (serverHandle, itemHandle) (returns true to deny).</summary>
    public Func<int, int, bool> DenyReadHandle { get; init; } = (_, _) => false;

    /// <summary>Deny predicate keyed on (serverHandle, itemHandle) (returns true to deny).</summary>
    public Func<int, int, bool> DenyWriteHandle { get; init; } = (_, _) => false;

    /// <summary>Recorded denial messages — (commandKind, target, correlationId) tuples.</summary>
    public List<(string CommandKind, string Target, string? CorrelationId)> RecordedDenials { get; } = [];

    /// <inheritdoc />
    public Task<ConstraintFailure?> CheckReadTagAsync(
        ApiKeyIdentity? identity,
        string tagAddress,
        CancellationToken cancellationToken)
    {
        if (DenyTag(tagAddress))
        {
            return Task.FromResult<ConstraintFailure?>(
                new ConstraintFailure("read-tag", $"Read denied for tag '{tagAddress}'."));
        }

        return Task.FromResult<ConstraintFailure?>(null);
    }

    /// <inheritdoc />
    public Task<ConstraintFailure?> CheckReadHandleAsync(
        ApiKeyIdentity? identity,
        GatewaySession session,
        int serverHandle,
        int itemHandle,
        CancellationToken cancellationToken)
    {
        if (DenyReadHandle(serverHandle, itemHandle))
        {
            return Task.FromResult<ConstraintFailure?>(
                new ConstraintFailure("read-handle", $"Read denied for handle {itemHandle}."));
        }

        return Task.FromResult<ConstraintFailure?>(null);
    }

    /// <inheritdoc />
    public Task<ConstraintFailure?> CheckWriteHandleAsync(
        ApiKeyIdentity? identity,
        GatewaySession session,
        int serverHandle,
        int itemHandle,
        CancellationToken cancellationToken)
    {
        if (DenyWriteHandle(serverHandle, itemHandle))
        {
            return Task.FromResult<ConstraintFailure?>(
                new ConstraintFailure("write-handle", $"Write denied for handle {itemHandle}."));
        }

        return Task.FromResult<ConstraintFailure?>(null);
    }

    /// <inheritdoc />
    public Task RecordDenialAsync(
        ApiKeyIdentity? identity,
        string commandKind,
        string target,
        ConstraintFailure failure,
        string? correlationId,
        CancellationToken cancellationToken)
    {
        RecordedDenials.Add((commandKind, target, correlationId));
        return Task.CompletedTask;
    }
}
