using Microsoft.EntityFrameworkCore;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
using ZB.MOM.WW.OtOpcUa.Core.Authorization;

namespace ZB.MOM.WW.OtOpcUa.Admin.Services;

/// <summary>
///     Runs an ad-hoc permission probe against a draft or published generation's NodeAcl rows —
///     "if LDAP group X asks for permission Y on node Z, would the trie grant it, and which
///     rows contributed?" Powers the AclsTab "Probe this permission" form per the #196 sub-slice.
/// </summary>
/// <remarks>
///     Thin wrapper over <see cref="PermissionTrieBuilder"/> + <see cref="PermissionTrie.CollectMatches"/> —
///     the same code path the Server's dispatch layer uses at request time, so a probe result
///     is guaranteed to match what the live server would decide. The probe is read-only + has
///     no side effects; failing probes do NOT generate audit log rows.
/// </remarks>
public sealed class PermissionProbeService(OtOpcUaConfigDbContext db)
{
    /// <summary>
    ///     Evaluate <paramref name="required"/> against the NodeAcl rows of
    ///     <paramref name="generationId"/> for a request by <paramref name="ldapGroup"/> at
    ///     <paramref name="scope"/>. Returns whether the permission would be granted + the list
    ///     of matching grants so the UI can show *why*.
    /// </summary>
    public async Task<PermissionProbeResult> ProbeAsync(
        long generationId,
        string ldapGroup,
        NodeScope scope,
        NodePermissions required,
        CancellationToken ct)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(ldapGroup);
        ArgumentNullException.ThrowIfNull(scope);

        var rows = await db.NodeAcls.AsNoTracking()
            .Where(a => a.GenerationId == generationId && a.ClusterId == scope.ClusterId)
            .ToListAsync(ct).ConfigureAwait(false);

        var trie = PermissionTrieBuilder.Build(scope.ClusterId, generationId, rows);
        var matches = trie.CollectMatches(scope, [ldapGroup]);

        var effective = NodePermissions.None;
        foreach (var m in matches)
            effective |= m.PermissionFlags;

        var granted = (effective & required) == required;
        return new PermissionProbeResult(
            Granted: granted,
            Required: required,
            Effective: effective,
            Matches: matches);
    }
}

/// <summary>Outcome of a <see cref="PermissionProbeService.ProbeAsync"/> call.</summary>
public sealed record PermissionProbeResult(
    bool Granted,
    NodePermissions Required,
    NodePermissions Effective,
    IReadOnlyList<MatchedGrant> Matches);
