using Microsoft.EntityFrameworkCore;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Admin.Services;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;

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

[Trait("Category", "Unit")]
public sealed class ValidatedNodeAclAuthoringServiceTests : IDisposable
{
    private readonly OtOpcUaConfigDbContext _db;

    public ValidatedNodeAclAuthoringServiceTests()
    {
        var options = new DbContextOptionsBuilder<OtOpcUaConfigDbContext>()
            .UseInMemoryDatabase($"val-nodeacl-{Guid.NewGuid():N}")
            .Options;
        _db = new OtOpcUaConfigDbContext(options);
    }

    public void Dispose() => _db.Dispose();

    [Fact]
    public async Task Grant_Rejects_NonePermissions()
    {
        var svc = new ValidatedNodeAclAuthoringService(_db);

        await Should.ThrowAsync<InvalidNodeAclGrantException>(() => svc.GrantAsync(
            draftGenerationId: 1, clusterId: "c1", ldapGroup: "cn=ops",
            scopeKind: NodeAclScopeKind.Cluster, scopeId: null,
            permissions: NodePermissions.None, notes: null, CancellationToken.None));
    }

    [Fact]
    public async Task Grant_Rejects_ClusterScope_With_ScopeId()
    {
        var svc = new ValidatedNodeAclAuthoringService(_db);

        await Should.ThrowAsync<InvalidNodeAclGrantException>(() => svc.GrantAsync(
            1, "c1", "cn=ops",
            NodeAclScopeKind.Cluster, scopeId: "not-null-wrong",
            NodePermissions.Read, null, CancellationToken.None));
    }

    [Fact]
    public async Task Grant_Rejects_SubClusterScope_Without_ScopeId()
    {
        var svc = new ValidatedNodeAclAuthoringService(_db);

        await Should.ThrowAsync<InvalidNodeAclGrantException>(() => svc.GrantAsync(
            1, "c1", "cn=ops",
            NodeAclScopeKind.Equipment, scopeId: null,
            NodePermissions.Read, null, CancellationToken.None));
    }

    [Fact]
    public async Task Grant_Succeeds_When_Valid()
    {
        var svc = new ValidatedNodeAclAuthoringService(_db);

        var row = await svc.GrantAsync(
            1, "c1", "cn=ops",
            NodeAclScopeKind.Cluster, null,
            NodePermissions.Read | NodePermissions.Browse, "fleet reader", CancellationToken.None);

        row.LdapGroup.ShouldBe("cn=ops");
        row.PermissionFlags.ShouldBe(NodePermissions.Read | NodePermissions.Browse);
        row.NodeAclId.ShouldNotBeNullOrWhiteSpace();
    }

    [Fact]
    public async Task Grant_Rejects_DuplicateScopeGroup_Pair()
    {
        var svc = new ValidatedNodeAclAuthoringService(_db);
        await svc.GrantAsync(1, "c1", "cn=ops", NodeAclScopeKind.Cluster, null,
            NodePermissions.Read, null, CancellationToken.None);

        await Should.ThrowAsync<InvalidNodeAclGrantException>(() => svc.GrantAsync(
            1, "c1", "cn=ops", NodeAclScopeKind.Cluster, null,
            NodePermissions.WriteOperate, null, CancellationToken.None));
    }

    [Fact]
    public async Task Grant_SameGroup_DifferentScope_IsAllowed()
    {
        var svc = new ValidatedNodeAclAuthoringService(_db);
        await svc.GrantAsync(1, "c1", "cn=ops", NodeAclScopeKind.Cluster, null,
            NodePermissions.Read, null, CancellationToken.None);

        var tagRow = await svc.GrantAsync(1, "c1", "cn=ops",
            NodeAclScopeKind.Tag, scopeId: "tag-xyz",
            NodePermissions.WriteOperate, null, CancellationToken.None);

        tagRow.ScopeKind.ShouldBe(NodeAclScopeKind.Tag);
    }

    [Fact]
    public async Task Grant_SameGroupScope_DifferentDraft_IsAllowed()
    {
        var svc = new ValidatedNodeAclAuthoringService(_db);
        await svc.GrantAsync(1, "c1", "cn=ops", NodeAclScopeKind.Cluster, null,
            NodePermissions.Read, null, CancellationToken.None);

        var draft2Row = await svc.GrantAsync(2, "c1", "cn=ops",
            NodeAclScopeKind.Cluster, null,
            NodePermissions.Read, null, CancellationToken.None);

        draft2Row.GenerationId.ShouldBe(2);
    }

    [Fact]
    public async Task UpdatePermissions_Rejects_None()
    {
        var svc = new ValidatedNodeAclAuthoringService(_db);
        var row = await svc.GrantAsync(1, "c1", "cn=ops", NodeAclScopeKind.Cluster, null,
            NodePermissions.Read, null, CancellationToken.None);

        await Should.ThrowAsync<InvalidNodeAclGrantException>(
            () => svc.UpdatePermissionsAsync(row.NodeAclRowId, NodePermissions.None, null, CancellationToken.None));
    }

    [Fact]
    public async Task UpdatePermissions_RoundTrips_NewFlags()
    {
        var svc = new ValidatedNodeAclAuthoringService(_db);
        var row = await svc.GrantAsync(1, "c1", "cn=ops", NodeAclScopeKind.Cluster, null,
            NodePermissions.Read, null, CancellationToken.None);

        var updated = await svc.UpdatePermissionsAsync(row.NodeAclRowId,
            NodePermissions.Read | NodePermissions.WriteOperate, "bumped", CancellationToken.None);

        updated.PermissionFlags.ShouldBe(NodePermissions.Read | NodePermissions.WriteOperate);
        updated.Notes.ShouldBe("bumped");
    }

    [Fact]
    public async Task UpdatePermissions_MissingRow_Throws()
    {
        var svc = new ValidatedNodeAclAuthoringService(_db);

        await Should.ThrowAsync<InvalidNodeAclGrantException>(
            () => svc.UpdatePermissionsAsync(Guid.NewGuid(), NodePermissions.Read, null, CancellationToken.None));
    }
}
