using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Server.Galaxy;

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

/// <summary>
///     Coverage for <see cref="GalaxyHierarchyIndex.ChildrenByParent"/> — the parent→children
///     index used by the lazy browse projector (Task 3). Verifies root grouping, nested
///     parent→child linkage, corrupt self-parented row handling, and the areas-first
///     ordering rule shared with <c>DashboardBrowseTreeBuilder</c>.
/// </summary>
public sealed class GalaxyHierarchyIndexTests
{
    /// <summary>Verifies roots (ParentGobjectId == 0) bucket under sentinel key 0.</summary>
    [Fact]
    public void ChildrenByParent_RootsUnderSentinelZero()
    {
        GalaxyObject root1 = new() { GobjectId = 1, ParentGobjectId = 0, ContainedName = "r1" };
        GalaxyObject root2 = new() { GobjectId = 2, ParentGobjectId = 0, ContainedName = "r2" };
        GalaxyObject root3 = new() { GobjectId = 3, ParentGobjectId = 0, ContainedName = "r3" };

        GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([root1, root2, root3]);

        Assert.True(index.ChildrenByParent.TryGetValue(0, out IReadOnlyList<GalaxyObjectView>? roots));
        Assert.NotNull(roots);
        Assert.Equal(3, roots!.Count);
        Assert.Contains(roots, view => view.Object.GobjectId == 1);
        Assert.Contains(roots, view => view.Object.GobjectId == 2);
        Assert.Contains(roots, view => view.Object.GobjectId == 3);
    }

    /// <summary>Verifies a nested A→B→C chain links each parent to its single child bucket.</summary>
    [Fact]
    public void ChildrenByParent_NestedHierarchy_LinksParentToChildren()
    {
        GalaxyObject areaA = new() { GobjectId = 1, ParentGobjectId = 0, IsArea = true, ContainedName = "A" };
        GalaxyObject objB = new() { GobjectId = 2, ParentGobjectId = 1, ContainedName = "B" };
        GalaxyObject objC = new() { GobjectId = 3, ParentGobjectId = 2, ContainedName = "C" };

        GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([areaA, objB, objC]);

        Assert.True(index.ChildrenByParent.TryGetValue(0, out IReadOnlyList<GalaxyObjectView>? underRoot));
        Assert.NotNull(underRoot);
        Assert.Single(underRoot!);
        Assert.Equal(1, underRoot![0].Object.GobjectId);

        Assert.True(index.ChildrenByParent.TryGetValue(1, out IReadOnlyList<GalaxyObjectView>? underA));
        Assert.NotNull(underA);
        Assert.Single(underA!);
        Assert.Equal(2, underA![0].Object.GobjectId);

        Assert.True(index.ChildrenByParent.TryGetValue(2, out IReadOnlyList<GalaxyObjectView>? underB));
        Assert.NotNull(underB);
        Assert.Single(underB!);
        Assert.Equal(3, underB![0].Object.GobjectId);
    }

    /// <summary>Verifies a self-parented (corrupt) row appears under root, not under itself.</summary>
    [Fact]
    public void ChildrenByParent_SelfParentedObject_AppearsAsRoot()
    {
        GalaxyObject selfParented = new() { GobjectId = 5, ParentGobjectId = 5, ContainedName = "loop" };

        GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([selfParented]);

        Assert.True(index.ChildrenByParent.TryGetValue(0, out IReadOnlyList<GalaxyObjectView>? roots));
        Assert.NotNull(roots);
        Assert.Single(roots!);
        Assert.Equal(5, roots![0].Object.GobjectId);

        // The self-parented row must not appear as its own child — bucket either absent or empty.
        if (index.ChildrenByParent.TryGetValue(5, out IReadOnlyList<GalaxyObjectView>? underSelf))
        {
            Assert.Empty(underSelf!);
        }
    }

    /// <summary>
    ///     Verifies an object whose parent is absent from the set (e.g. a deleted/undeployed
    ///     container area) is re-rooted under sentinel 0 rather than vanishing under a phantom
    ///     parent id that browse never reaches from the root.
    /// </summary>
    [Fact]
    public void ChildrenByParent_OrphanWithMissingParent_AppearsAsRoot()
    {
        GalaxyObject realRoot = new() { GobjectId = 1, ParentGobjectId = 0, IsArea = true, ContainedName = "RealRoot" };
        GalaxyObject orphanArea = new() { GobjectId = 2, ParentGobjectId = 5008, IsArea = true, ContainedName = "Orphan" };
        GalaxyObject orphanChild = new() { GobjectId = 3, ParentGobjectId = 2, ContainedName = "Child" };

        GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([realRoot, orphanArea, orphanChild]);

        // Both the real root and the orphan (its parent 5008 is absent) surface under root.
        Assert.True(index.ChildrenByParent.TryGetValue(0, out IReadOnlyList<GalaxyObjectView>? roots));
        Assert.NotNull(roots);
        Assert.Contains(roots!, view => view.Object.GobjectId == 1);
        Assert.Contains(roots!, view => view.Object.GobjectId == 2);

        // The orphan keeps its own deployed children nested beneath it.
        Assert.True(index.ChildrenByParent.TryGetValue(2, out IReadOnlyList<GalaxyObjectView>? underOrphan));
        Assert.Single(underOrphan!);
        Assert.Equal(3, underOrphan![0].Object.GobjectId);

        // Nothing buckets under the phantom parent id.
        Assert.False(index.ChildrenByParent.ContainsKey(5008));
    }

    /// <summary>Verifies <see cref="GalaxyHierarchyIndex.ObjectViewsByTagName"/> is OrdinalIgnoreCase and supports O(1) lookups.</summary>
    [Fact]
    public void ObjectViewsByTagName_IsCaseInsensitive_AndLookupsAreO1()
    {
        GalaxyObject root = new() { GobjectId = 1, ParentGobjectId = 0, IsArea = true, ContainedName = "Plant", BrowseName = "Plant", TagName = "Plant" };
        GalaxyObject mixer = new() { GobjectId = 2, ParentGobjectId = 1, ContainedName = "Mixer_001", BrowseName = "Mixer_001", TagName = "Plant.Mixer_001" };

        GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([root, mixer]);

        Assert.True(index.ObjectViewsByTagName.TryGetValue("Plant.Mixer_001", out GalaxyObjectView? exact));
        Assert.NotNull(exact);
        Assert.Equal(2, exact!.Object.GobjectId);

        // Case-insensitive lookup must hit the same entry.
        Assert.True(index.ObjectViewsByTagName.TryGetValue("plant.mixer_001", out GalaxyObjectView? lower));
        Assert.NotNull(lower);
        Assert.Same(exact, lower);

        Assert.False(index.ObjectViewsByTagName.ContainsKey("Plant.Missing"));
    }

    /// <summary>Verifies <see cref="GalaxyHierarchyIndex.ObjectViewsByContainedPath"/> is OrdinalIgnoreCase.</summary>
    [Fact]
    public void ObjectViewsByContainedPath_IsCaseInsensitive()
    {
        GalaxyObject root = new() { GobjectId = 1, ParentGobjectId = 0, IsArea = true, ContainedName = "Plant", BrowseName = "Plant", TagName = "Plant" };
        GalaxyObject lineA = new() { GobjectId = 2, ParentGobjectId = 1, IsArea = true, ContainedName = "Line_A", BrowseName = "Line_A", TagName = "Plant.Line_A" };

        GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([root, lineA]);

        Assert.True(index.ObjectViewsByContainedPath.TryGetValue("Plant/Line_A", out GalaxyObjectView? exact));
        Assert.NotNull(exact);
        Assert.Equal(2, exact!.Object.GobjectId);

        Assert.True(index.ObjectViewsByContainedPath.TryGetValue("plant/line_a", out GalaxyObjectView? lower));
        Assert.NotNull(lower);
        Assert.Same(exact, lower);

        Assert.False(index.ObjectViewsByContainedPath.ContainsKey("Plant/Missing"));
    }

    /// <summary>Verifies children sort areas-first, then by display name (case-insensitive).</summary>
    [Fact]
    public void ChildrenByParent_SortsAreasFirstThenByDisplayName()
    {
        GalaxyObject parent = new() { GobjectId = 1, ParentGobjectId = 0, IsArea = true, ContainedName = "Root" };
        GalaxyObject zebraObj = new() { GobjectId = 10, ParentGobjectId = 1, IsArea = false, ContainedName = "zebra" };
        GalaxyObject alphaArea = new() { GobjectId = 11, ParentGobjectId = 1, IsArea = true, ContainedName = "alpha" };
        GalaxyObject betaArea = new() { GobjectId = 12, ParentGobjectId = 1, IsArea = true, ContainedName = "beta" };

        GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([parent, zebraObj, alphaArea, betaArea]);

        Assert.True(index.ChildrenByParent.TryGetValue(1, out IReadOnlyList<GalaxyObjectView>? children));
        Assert.NotNull(children);
        Assert.Equal(3, children!.Count);
        Assert.Equal(11, children[0].Object.GobjectId);
        Assert.Equal(12, children[1].Object.GobjectId);
        Assert.Equal(10, children[2].Object.GobjectId);
    }
}
