@using Microsoft.AspNetCore.SignalR.Client
@using ZB.MOM.WW.OtOpcUa.Admin.Hubs
@using ZB.MOM.WW.OtOpcUa.Admin.Services
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
@using ZB.MOM.WW.OtOpcUa.Configuration.Enums
@using ZB.MOM.WW.OtOpcUa.Core.Authorization
@inject NodeAclService AclSvc
@inject PermissionProbeService ProbeSvc
@inject NavigationManager Nav
@implements IAsyncDisposable

<div class="d-flex justify-content-between mb-3">
    <h4>Access-control grants</h4>
    <button class="btn btn-sm btn-primary" @onclick="() => _showForm = true">Add grant</button>
</div>

@if (_acls is null) { <p>Loading…</p> }
else if (_acls.Count == 0) { <p class="text-muted">No ACL grants in this draft. Publish will result in a cluster with no external access.</p> }
else
{
    <table class="table table-sm">
        <thead><tr><th>LDAP group</th><th>Scope</th><th>Scope ID</th><th>Permissions</th><th></th></tr></thead>
        <tbody>
        @foreach (var a in _acls)
        {
            <tr>
                <td>@a.LdapGroup</td>
                <td>@a.ScopeKind</td>
                <td><code>@(a.ScopeId ?? "-")</code></td>
                <td><code>@a.PermissionFlags</code></td>
                <td><button class="btn btn-sm btn-outline-danger" @onclick="() => RevokeAsync(a.NodeAclRowId)">Revoke</button></td>
            </tr>
        }
        </tbody>
    </table>
}

@* Probe-this-permission — task #196 slice 1 *@
<div class="card mt-4 mb-3">
    <div class="card-header">
        <strong>Probe this permission</strong>
        <span class="small text-muted ms-2">
            Ask the trie "if LDAP group X asks for permission Y on node Z, would it be granted?" —
            answers the same way the live server does at request time.
        </span>
    </div>
    <div class="card-body">
        <div class="row g-2 align-items-end">
            <div class="col-md-3">
                <label class="form-label small">LDAP group</label>
                <input class="form-control form-control-sm" @bind="_probeGroup" placeholder="cn=fleet-admin,…"/>
            </div>
            <div class="col-md-2">
                <label class="form-label small">Namespace</label>
                <input class="form-control form-control-sm" @bind="_probeNamespaceId" placeholder="ns-1"/>
            </div>
            <div class="col-md-2">
                <label class="form-label small">UnsArea</label>
                <input class="form-control form-control-sm" @bind="_probeUnsAreaId"/>
            </div>
            <div class="col-md-2">
                <label class="form-label small">UnsLine</label>
                <input class="form-control form-control-sm" @bind="_probeUnsLineId"/>
            </div>
            <div class="col-md-1">
                <label class="form-label small">Equipment</label>
                <input class="form-control form-control-sm" @bind="_probeEquipmentId"/>
            </div>
            <div class="col-md-1">
                <label class="form-label small">Tag</label>
                <input class="form-control form-control-sm" @bind="_probeTagId"/>
            </div>
            <div class="col-md-1">
                <label class="form-label small">Permission</label>
                <select class="form-select form-select-sm" @bind="_probePermission">
                    @foreach (var p in Enum.GetValues<NodePermissions>())
                    {
                        if (p == NodePermissions.None) continue;
                        <option value="@p">@p</option>
                    }
                </select>
            </div>
        </div>
        <div class="mt-3">
            <button class="btn btn-sm btn-outline-primary" @onclick="RunProbeAsync" disabled="@_probing">Probe</button>
            @if (_probeResult is not null)
            {
                <span class="ms-3">
                    @if (_probeResult.Granted)
                    {
                        <span class="badge bg-success">Granted</span>
                    }
                    else
                    {
                        <span class="badge bg-danger">Denied</span>
                    }
                    <span class="small ms-2">
                        Required <code>@_probeResult.Required</code>,
                        Effective <code>@_probeResult.Effective</code>
                    </span>
                </span>
            }
        </div>
        @if (_probeResult is not null && _probeResult.Matches.Count > 0)
        {
            <table class="table table-sm mt-3 mb-0">
                <thead><tr><th>LDAP group matched</th><th>Level</th><th>Flags contributed</th></tr></thead>
                <tbody>
                @foreach (var m in _probeResult.Matches)
                {
                    <tr>
                        <td><code>@m.LdapGroup</code></td>
                        <td>@m.Scope</td>
                        <td><code>@m.PermissionFlags</code></td>
                    </tr>
                }
                </tbody>
            </table>
        }
        else if (_probeResult is not null)
        {
            <div class="mt-2 small text-muted">No matching grants for this (group, scope) — effective permission is <code>None</code>.</div>
        }
    </div>
</div>

@if (_showForm)
{
    <div class="card">
        <div class="card-body">
            <div class="row g-3">
                <div class="col-md-4">
                    <label class="form-label">LDAP group</label>
                    <input class="form-control" @bind="_group"/>
                </div>
                <div class="col-md-4">
                    <label class="form-label">Scope kind</label>
                    <select class="form-select" @bind="_scopeKind">
                        @foreach (var k in Enum.GetValues<NodeAclScopeKind>()) { <option value="@k">@k</option> }
                    </select>
                </div>
                <div class="col-md-4">
                    <label class="form-label">Scope ID (empty for Cluster-wide)</label>
                    <input class="form-control" @bind="_scopeId"/>
                </div>
                <div class="col-12">
                    <label class="form-label">Permissions (bundled presets — per-flag editor in v2.1)</label>
                    <select class="form-select" @bind="_preset">
                        <option value="Read">Read (Browse + Read)</option>
                        <option value="WriteOperate">Read + Write Operate</option>
                        <option value="Engineer">Read + Write Tune + Write Configure</option>
                        <option value="AlarmAck">Read + Alarm Ack</option>
                        <option value="Full">Full (every flag)</option>
                    </select>
                </div>
            </div>
            @if (_error is not null) { <div class="alert alert-danger mt-3">@_error</div> }
            <div class="mt-3">
                <button class="btn btn-sm btn-primary" @onclick="SaveAsync">Save</button>
                <button class="btn btn-sm btn-secondary ms-2" @onclick="() => _showForm = false">Cancel</button>
            </div>
        </div>
    </div>
}

@code {
    [Parameter] public long GenerationId { get; set; }
    [Parameter] public string ClusterId { get; set; } = string.Empty;

    private List<NodeAcl>? _acls;
    private bool _showForm;
    private string _group = string.Empty;
    private NodeAclScopeKind _scopeKind = NodeAclScopeKind.Cluster;
    private string _scopeId = string.Empty;
    private string _preset = "Read";
    private string? _error;

    // Probe-this-permission state
    private string _probeGroup = string.Empty;
    private string _probeNamespaceId = string.Empty;
    private string _probeUnsAreaId = string.Empty;
    private string _probeUnsLineId = string.Empty;
    private string _probeEquipmentId = string.Empty;
    private string _probeTagId = string.Empty;
    private NodePermissions _probePermission = NodePermissions.Read;
    private PermissionProbeResult? _probeResult;
    private bool _probing;

    private async Task RunProbeAsync()
    {
        if (string.IsNullOrWhiteSpace(_probeGroup)) { _probeResult = null; return; }
        _probing = true;
        try
        {
            var scope = new NodeScope
            {
                ClusterId = ClusterId,
                NamespaceId = NullIfBlank(_probeNamespaceId),
                UnsAreaId = NullIfBlank(_probeUnsAreaId),
                UnsLineId = NullIfBlank(_probeUnsLineId),
                EquipmentId = NullIfBlank(_probeEquipmentId),
                TagId = NullIfBlank(_probeTagId),
                Kind = NodeHierarchyKind.Equipment,
            };
            _probeResult = await ProbeSvc.ProbeAsync(GenerationId, _probeGroup.Trim(), scope, _probePermission, CancellationToken.None);
        }
        finally { _probing = false; }
    }

    private static string? NullIfBlank(string s) => string.IsNullOrWhiteSpace(s) ? null : s;

    private HubConnection? _hub;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender || _hub is not null) return;
        _hub = new HubConnectionBuilder()
            .WithUrl(Nav.ToAbsoluteUri("/hubs/fleet-status"))
            .WithAutomaticReconnect()
            .Build();
        _hub.On<NodeAclChangedMessage>("NodeAclChanged", async msg =>
        {
            if (msg.ClusterId != ClusterId || msg.GenerationId != GenerationId) return;
            _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None);
            await InvokeAsync(StateHasChanged);
        });
        await _hub.StartAsync();
        await _hub.SendAsync("SubscribeCluster", ClusterId);
    }

    public async ValueTask DisposeAsync()
    {
        if (_hub is not null) { await _hub.DisposeAsync(); _hub = null; }
    }

    protected override async Task OnParametersSetAsync() =>
        _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None);

    private NodePermissions ResolvePreset() => _preset switch
    {
        "Read"          => NodePermissions.Browse | NodePermissions.Read,
        "WriteOperate"  => NodePermissions.Browse | NodePermissions.Read | NodePermissions.WriteOperate,
        "Engineer"      => NodePermissions.Browse | NodePermissions.Read | NodePermissions.WriteTune | NodePermissions.WriteConfigure,
        "AlarmAck"      => NodePermissions.Browse | NodePermissions.Read | NodePermissions.AlarmRead | NodePermissions.AlarmAcknowledge,
        "Full"          => unchecked((NodePermissions)(-1)),
        _               => NodePermissions.Browse | NodePermissions.Read,
    };

    private async Task SaveAsync()
    {
        _error = null;
        if (string.IsNullOrWhiteSpace(_group)) { _error = "LDAP group is required"; return; }

        var scopeId = _scopeKind == NodeAclScopeKind.Cluster ? null
                     : string.IsNullOrWhiteSpace(_scopeId) ? null : _scopeId;

        if (_scopeKind != NodeAclScopeKind.Cluster && scopeId is null)
        {
            _error = $"ScopeId required for {_scopeKind}";
            return;
        }

        try
        {
            await AclSvc.GrantAsync(GenerationId, ClusterId, _group, _scopeKind, scopeId,
                ResolvePreset(), notes: null, CancellationToken.None);
            _group = string.Empty; _scopeId = string.Empty;
            _showForm = false;
            _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None);
        }
        catch (Exception ex) { _error = ex.Message; }
    }

    private async Task RevokeAsync(Guid rowId)
    {
        await AclSvc.RevokeAsync(rowId, CancellationToken.None);
        _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None);
    }
}
