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

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

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

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

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

    private LdapGroupRoleMapping Make(string group, AdminRole role, string? clusterId = null, bool? isSystemWide = null) =>
        new()
        {
            LdapGroup = group,
            Role = role,
            ClusterId = clusterId,
            IsSystemWide = isSystemWide ?? (clusterId is null),
        };

    [Fact]
    public async Task Create_SetsId_AndCreatedAtUtc()
    {
        var svc = new LdapGroupRoleMappingService(_db);
        var row = Make("cn=fleet,dc=x", AdminRole.FleetAdmin);

        var saved = await svc.CreateAsync(row, CancellationToken.None);

        saved.Id.ShouldNotBe(Guid.Empty);
        saved.CreatedAtUtc.ShouldBeGreaterThan(DateTime.UtcNow.AddMinutes(-1));
    }

    [Fact]
    public async Task Create_Rejects_EmptyLdapGroup()
    {
        var svc = new LdapGroupRoleMappingService(_db);
        var row = Make("", AdminRole.FleetAdmin);

        await Should.ThrowAsync<InvalidLdapGroupRoleMappingException>(
            () => svc.CreateAsync(row, CancellationToken.None));
    }

    [Fact]
    public async Task Create_Rejects_SystemWide_With_ClusterId()
    {
        var svc = new LdapGroupRoleMappingService(_db);
        var row = Make("cn=g", AdminRole.ConfigViewer, clusterId: "c1", isSystemWide: true);

        await Should.ThrowAsync<InvalidLdapGroupRoleMappingException>(
            () => svc.CreateAsync(row, CancellationToken.None));
    }

    [Fact]
    public async Task Create_Rejects_NonSystemWide_WithoutClusterId()
    {
        var svc = new LdapGroupRoleMappingService(_db);
        var row = Make("cn=g", AdminRole.ConfigViewer, clusterId: null, isSystemWide: false);

        await Should.ThrowAsync<InvalidLdapGroupRoleMappingException>(
            () => svc.CreateAsync(row, CancellationToken.None));
    }

    [Fact]
    public async Task GetByGroups_Returns_MatchingGrants_Only()
    {
        var svc = new LdapGroupRoleMappingService(_db);
        await svc.CreateAsync(Make("cn=fleet,dc=x", AdminRole.FleetAdmin), CancellationToken.None);
        await svc.CreateAsync(Make("cn=editor,dc=x", AdminRole.ConfigEditor), CancellationToken.None);
        await svc.CreateAsync(Make("cn=viewer,dc=x", AdminRole.ConfigViewer), CancellationToken.None);

        var results = await svc.GetByGroupsAsync(
            ["cn=fleet,dc=x", "cn=viewer,dc=x"], CancellationToken.None);

        results.Count.ShouldBe(2);
        results.Select(r => r.Role).ShouldBe([AdminRole.FleetAdmin, AdminRole.ConfigViewer], ignoreOrder: true);
    }

    [Fact]
    public async Task GetByGroups_Empty_Input_ReturnsEmpty()
    {
        var svc = new LdapGroupRoleMappingService(_db);
        await svc.CreateAsync(Make("cn=fleet,dc=x", AdminRole.FleetAdmin), CancellationToken.None);

        var results = await svc.GetByGroupsAsync([], CancellationToken.None);

        results.ShouldBeEmpty();
    }

    [Fact]
    public async Task ListAll_Orders_ByGroupThenCluster()
    {
        var svc = new LdapGroupRoleMappingService(_db);
        await svc.CreateAsync(Make("cn=b,dc=x", AdminRole.FleetAdmin), CancellationToken.None);
        await svc.CreateAsync(Make("cn=a,dc=x", AdminRole.ConfigEditor, clusterId: "c2", isSystemWide: false), CancellationToken.None);
        await svc.CreateAsync(Make("cn=a,dc=x", AdminRole.ConfigEditor, clusterId: "c1", isSystemWide: false), CancellationToken.None);

        var results = await svc.ListAllAsync(CancellationToken.None);

        results[0].LdapGroup.ShouldBe("cn=a,dc=x");
        results[0].ClusterId.ShouldBe("c1");
        results[1].ClusterId.ShouldBe("c2");
        results[2].LdapGroup.ShouldBe("cn=b,dc=x");
    }

    [Fact]
    public async Task Delete_Removes_Matching_Row()
    {
        var svc = new LdapGroupRoleMappingService(_db);
        var saved = await svc.CreateAsync(Make("cn=fleet,dc=x", AdminRole.FleetAdmin), CancellationToken.None);

        await svc.DeleteAsync(saved.Id, CancellationToken.None);

        var after = await svc.ListAllAsync(CancellationToken.None);
        after.ShouldBeEmpty();
    }

    [Fact]
    public async Task Delete_Unknown_Id_IsNoOp()
    {
        var svc = new LdapGroupRoleMappingService(_db);

        await svc.DeleteAsync(Guid.NewGuid(), CancellationToken.None);
        // no exception
    }
}
