using Microsoft.Extensions.Options;
using ZB.MOM.WW.MxGateway.Server.Configuration;

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

/// <summary>
/// Covers the projection <see cref="GatewayConfigurationProvider"/> makes from bound
/// <see cref="GatewayOptions"/> onto the effective-configuration record the dashboard renders.
/// </summary>
/// <remarks>
/// The projection is hand-written member by member, so an option that exists and validates can
/// still be invisible on the settings page — which is exactly what happened to the two SEC-25
/// dashboard-ACL options. These tests pin the dashboard-tag members to the options they come
/// from; the redaction assertions pin the opposite invariant, that the two secret-bearing members
/// are the only ones masked.
/// </remarks>
public sealed class GatewayConfigurationProviderTests
{
    /// <summary>The group → tag map reaches the projection with its groups, tags and ordering intact.</summary>
    [Fact]
    public void GetEffectiveConfiguration_CopiesGroupToTagMapping()
    {
        GatewayOptions options = new()
        {
            Dashboard = new DashboardOptions
            {
                GroupToTag = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
                {
                    // A multi-tag group is the interesting case: a projection that flattened the
                    // array to its first element, or to a joined string, would still satisfy a
                    // single-tag fixture.
                    ["GwOps"] = ["team-a", "team-b"],
                    ["GwViewers"] = ["team-a"],
                },
            },
        };

        EffectiveDashboardConfiguration dashboard = Project(options).Dashboard;

        Assert.Equal(2, dashboard.GroupToTag.Count);
        Assert.Equal(["team-a", "team-b"], dashboard.GroupToTag["GwOps"]);
        Assert.Equal(["team-a"], dashboard.GroupToTag["GwViewers"]);
    }

    /// <summary>
    /// The map's case-insensitive lookup survives the projection. LDAP group names arrive in
    /// whatever case the directory returns them, so a projection that rebuilt the dictionary with
    /// the default ordinal comparer would silently stop matching.
    /// </summary>
    [Fact]
    public void GetEffectiveConfiguration_KeepsGroupToTagLookupCaseInsensitive()
    {
        GatewayOptions options = new()
        {
            Dashboard = new DashboardOptions
            {
                GroupToTag = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
                {
                    ["GwOps"] = ["team-a"],
                },
            },
        };

        EffectiveDashboardConfiguration dashboard = Project(options).Dashboard;

        Assert.True(dashboard.GroupToTag.ContainsKey("gwops"));
    }

    /// <summary>
    /// An unconfigured map projects as empty rather than null — the settings page renders the row
    /// either way, and "none configured" is the operationally interesting answer.
    /// </summary>
    [Fact]
    public void GetEffectiveConfiguration_WhenNoTagsConfigured_ProjectsEmptyMapping()
    {
        EffectiveDashboardConfiguration dashboard = Project(new GatewayOptions()).Dashboard;

        Assert.Empty(dashboard.GroupToTag);
        Assert.Empty(dashboard.GroupToRole);
    }

    /// <summary>The untagged-session visibility policy is projected, defaulting to the fail-closed value.</summary>
    [Fact]
    public void GetEffectiveConfiguration_CopiesUntaggedSessionVisibility()
    {
        Assert.Equal(
            UntaggedSessionVisibility.AdminOnly,
            Project(new GatewayOptions()).Dashboard.UntaggedSessionVisibility);

        GatewayOptions widened = new()
        {
            Dashboard = new DashboardOptions
            {
                UntaggedSessionVisibility = UntaggedSessionVisibility.AllViewers,
            },
        };

        Assert.Equal(
            UntaggedSessionVisibility.AllViewers,
            Project(widened).Dashboard.UntaggedSessionVisibility);
    }

    /// <summary>The pre-existing dashboard members keep coming from the options they always did.</summary>
    [Fact]
    public void GetEffectiveConfiguration_CopiesTheOtherDashboardMembers()
    {
        GatewayOptions options = new()
        {
            Dashboard = new DashboardOptions
            {
                Enabled = false,
                AllowAnonymousLocalhost = false,
                SnapshotIntervalMilliseconds = 2_500,
                RecentFaultLimit = 7,
                RecentSessionLimit = 11,
                ShowTagValues = true,
                GroupToRole = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
                {
                    ["GwAdmins"] = "Administrator",
                },
            },
        };

        EffectiveDashboardConfiguration dashboard = Project(options).Dashboard;

        Assert.False(dashboard.Enabled);
        Assert.False(dashboard.AllowAnonymousLocalhost);
        Assert.Equal(2_500, dashboard.SnapshotIntervalMilliseconds);
        Assert.Equal(7, dashboard.RecentFaultLimit);
        Assert.Equal(11, dashboard.RecentSessionLimit);
        Assert.True(dashboard.ShowTagValues);
        Assert.Equal("Administrator", dashboard.GroupToRole["GwAdmins"]);
    }

    /// <summary>
    /// The masking boundary. Only the pepper name and the LDAP bind password are redacted; the
    /// tag mapping is configuration, not a secret, so masking it would hide the very thing the
    /// settings page was extended to show.
    /// </summary>
    [Fact]
    public void GetEffectiveConfiguration_RedactsOnlyTheSecretBearingMembers()
    {
        GatewayOptions options = new()
        {
            Ldap = new LdapOptions { ServiceAccountPassword = "bind-password" },
            Dashboard = new DashboardOptions
            {
                GroupToTag = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
                {
                    ["GwOps"] = ["team-a"],
                },
            },
        };

        EffectiveGatewayConfiguration configuration = Project(options);

        Assert.Equal(GatewayConfigurationProvider.RedactedValue, configuration.Authentication.PepperSecretName);
        Assert.Equal(GatewayConfigurationProvider.RedactedValue, configuration.Ldap.ServiceAccountPassword);
        Assert.Equal(["team-a"], configuration.Dashboard.GroupToTag["GwOps"]);
    }

    private static EffectiveGatewayConfiguration Project(GatewayOptions options) =>
        new GatewayConfigurationProvider(Options.Create(options)).GetEffectiveConfiguration();
}
