using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening;

namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Types;

/// <summary>
/// Tests for <see cref="ValidationResult.SummarizeErrors"/> (followup #8) — the grouped,
/// capped summary that replaces the flat semicolon-joined deploy error dump.
/// </summary>
public class ValidationResultSummaryTests
{
    private static ValidationEntry Unbound(string canonicalName) =>
        ValidationEntry.Error(
            ValidationCategory.ConnectionBinding,
            $"Attribute '{canonicalName}' has a data source reference but no connection binding.",
            canonicalName);

    [Fact]
    public void SummarizeErrors_NoErrors_ReturnsEmpty()
    {
        Assert.Equal(string.Empty, ValidationResult.Success().SummarizeErrors());
    }

    [Fact]
    public void SummarizeErrors_LeadsWithTotalCount()
    {
        var result = new ValidationResult
        {
            Errors = [Unbound("A.X"), Unbound("A.Y"), Unbound("B.Z")]
        };

        var summary = result.SummarizeErrors();

        Assert.StartsWith("3 errors:", summary);
    }

    [Fact]
    public void SummarizeErrors_SingleError_UsesSingularNoun()
    {
        var result = new ValidationResult { Errors = [Unbound("A.X")] };
        Assert.StartsWith("1 error:", result.SummarizeErrors());
    }

    [Fact]
    public void SummarizeErrors_RollsUpUnboundAttributesByModuleWithCounts()
    {
        // 12 + 12 unbound across two LeakTest modules — the reported wall-of-text case.
        var errors = new List<ValidationEntry>();
        for (var i = 0; i < 12; i++) errors.Add(Unbound($"LeftReactorSide.LeakTest.Attr{i}"));
        for (var i = 0; i < 12; i++) errors.Add(Unbound($"RightReactorSide.LeakTest.Attr{i}"));

        var summary = new ValidationResult { Errors = errors }.SummarizeErrors();

        Assert.Contains("24 errors:", summary);
        Assert.Contains("ConnectionBinding (24)", summary);
        Assert.Contains("LeftReactorSide.LeakTest (12)", summary);
        Assert.Contains("RightReactorSide.LeakTest (12)", summary);
        // The full 24 individual clauses must NOT all appear inline.
        Assert.DoesNotContain("Attr11' has a data source reference", summary);
    }

    [Fact]
    public void SummarizeErrors_CapsModuleBreadthWithAndNMore()
    {
        // 10 distinct modules, cap at 3 → 7 collapsed.
        var errors = Enumerable.Range(0, 10).Select(i => Unbound($"Module{i}.Attr")).ToList();

        var summary = new ValidationResult { Errors = errors }.SummarizeErrors(maxGroups: 3);

        Assert.Contains("… and 7 more module(s)", summary);
        Assert.Contains("Module0 (1)", summary);
    }

    [Fact]
    public void SummarizeErrors_TopLevelMember_GroupsUnderRoot()
    {
        var summary = new ValidationResult { Errors = [Unbound("Speed")] }.SummarizeErrors();
        Assert.Contains("(root) (1)", summary);
    }

    [Fact]
    public void SummarizeErrors_EntriesWithoutEntityName_FallBackToCappedMessageList()
    {
        var errors = Enumerable.Range(0, 5)
            .Select(i => ValidationEntry.Error(ValidationCategory.ScriptCompilation, $"compile error {i}"))
            .ToList();

        var summary = new ValidationResult { Errors = errors }.SummarizeErrors(maxGroups: 2);

        Assert.Contains("ScriptCompilation (5)", summary);
        Assert.Contains("compile error 0", summary);
        Assert.Contains("… and 3 more", summary);
    }

    [Fact]
    public void SummarizeErrors_MixedCategories_ListsEachCategoryGroup()
    {
        var result = new ValidationResult
        {
            Errors =
            [
                Unbound("A.X"),
                ValidationEntry.Error(ValidationCategory.NamingCollision, "dup name 'Foo'"),
            ]
        };

        var summary = result.SummarizeErrors();

        Assert.Contains("ConnectionBinding (1)", summary);
        Assert.Contains("NamingCollision (1)", summary);
    }
}
