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

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

/// <summary>
///     Direct coverage for <see cref="GalaxyHierarchyProjector"/> paging.
///     <para>
///         Regression guard for finding Server-007: the projector memoizes the filtered,
///         ordered view list per <c>(cache entry, filter signature)</c> so paging is an
///         O(pageSize) slice rather than an O(total) re-scan per page. These tests confirm
///         the memo does not change paging results, does not bleed between distinct filter
///         signatures, and is scoped to a single cache-entry instance.
///     </para>
/// </summary>
public sealed class GalaxyHierarchyProjectorTests
{
    [Fact]
    public void Project_PagedAcrossEntireHierarchy_ReturnsEveryObjectExactlyOnce()
    {
        GalaxyHierarchyCacheEntry entry = CreateEntry(CreateObjects(25));

        List<string> collected = [];
        int totalReported = -1;
        for (int offset = 0; offset < 25; offset += 4)
        {
            GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(
                entry,
                new DiscoverHierarchyRequest(),
                browseSubtreeGlobs: null,
                offset,
                pageSize: 4);

            totalReported = result.TotalObjectCount;
            collected.AddRange(result.Objects.Select(obj => obj.TagName));
        }

        Assert.Equal(25, totalReported);
        Assert.Equal(25, collected.Count);
        Assert.Equal(collected.Count, collected.Distinct(StringComparer.Ordinal).Count());
        Assert.Equal("Object_001", collected[0]);
        Assert.Equal("Object_025", collected[^1]);
    }

    [Fact]
    public void Project_DistinctFiltersOnSameEntry_DoNotShareMemoizedViewList()
    {
        GalaxyHierarchyCacheEntry entry = CreateEntry(CreateObjects(10));

        GalaxyHierarchyQueryResult globbed = GalaxyHierarchyProjector.Project(
            entry,
            new DiscoverHierarchyRequest { TagNameGlob = "Object_00?" });
        GalaxyHierarchyQueryResult unfiltered = GalaxyHierarchyProjector.Project(
            entry,
            new DiscoverHierarchyRequest());

        // Distinct filter signatures must each get their own filtered list.
        Assert.Equal(9, globbed.TotalObjectCount);
        Assert.Equal(10, unfiltered.TotalObjectCount);
    }

    [Fact]
    public void Project_SameFilterRepeated_ReturnsIdenticalTotals()
    {
        GalaxyHierarchyCacheEntry entry = CreateEntry(CreateObjects(12));

        GalaxyHierarchyQueryResult first = GalaxyHierarchyProjector.Project(
            entry,
            new DiscoverHierarchyRequest(),
            browseSubtreeGlobs: null,
            offset: 0,
            pageSize: 5);
        GalaxyHierarchyQueryResult second = GalaxyHierarchyProjector.Project(
            entry,
            new DiscoverHierarchyRequest(),
            browseSubtreeGlobs: null,
            offset: 5,
            pageSize: 5);

        Assert.Equal(first.TotalObjectCount, second.TotalObjectCount);
        Assert.Equal(first.FilterSignature, second.FilterSignature);
        Assert.Equal(5, first.Objects.Count);
        Assert.Equal(5, second.Objects.Count);
        Assert.NotEqual(first.Objects[0].TagName, second.Objects[0].TagName);
    }

    [Fact]
    public void Project_DistinctCacheEntries_ProjectAgainstTheirOwnData()
    {
        GalaxyHierarchyCacheEntry small = CreateEntry(CreateObjects(3));
        GalaxyHierarchyCacheEntry large = CreateEntry(CreateObjects(40));

        GalaxyHierarchyQueryResult smallResult = GalaxyHierarchyProjector.Project(
            small,
            new DiscoverHierarchyRequest());
        GalaxyHierarchyQueryResult largeResult = GalaxyHierarchyProjector.Project(
            large,
            new DiscoverHierarchyRequest());

        // Each entry instance keys its own memo; the second projection must not reuse the
        // first entry's filtered view list.
        Assert.Equal(3, smallResult.TotalObjectCount);
        Assert.Equal(40, largeResult.TotalObjectCount);
    }

    private static GalaxyHierarchyCacheEntry CreateEntry(IReadOnlyList<GalaxyObject> objects)
    {
        return GalaxyHierarchyCacheEntry.Empty with
        {
            Status = GalaxyCacheStatus.Healthy,
            Sequence = 1,
            LastSuccessAt = DateTimeOffset.UtcNow,
            Objects = objects,
            Index = GalaxyHierarchyIndex.Build(objects),
            DashboardSummary = DashboardGalaxySummary.Unknown with
            {
                Status = DashboardGalaxyStatus.Healthy,
                ObjectCount = objects.Count,
            },
            ObjectCount = objects.Count,
        };
    }

    private static IReadOnlyList<GalaxyObject> CreateObjects(int count)
    {
        return Enumerable.Range(1, count)
            .Select(index => new GalaxyObject
            {
                GobjectId = index,
                TagName = $"Object_{index:000}",
                BrowseName = $"Object_{index:000}",
            })
            .ToArray();
    }
}
