// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections;
using System.Runtime.CompilerServices;

using AwesomeAssertions;

using TestFramework.ForTestingMSTest;

namespace Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests;

public partial class AssertTests : TestContainer
{
    public void ContainsAll_Generic_AllPresent_ShouldPass()
        => Assert.ContainsAll([1, 2], [1, 2, 3]);

    public void ContainsAll_Generic_EmptyExpected_ShouldPass()
        => Assert.ContainsAll([], [1, 2, 3]);

    public void ContainsAll_Generic_DuplicatesWithinCollectionMultiplicity_ShouldPass()
        => Assert.ContainsAll([1, 1, 2], [1, 1, 2, 3]);

    public void ContainsAll_Generic_DuplicatesExceedCollectionMultiplicity_ShouldFail()
    {
        Action action = () => Assert.ContainsAll([1, 1, 1], [1, 1]);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to contain all specified items.

                missing:    [1]
                expected:   [1, 1, 1]
                collection: [1, 1]

                Assert.ContainsAll([1, 1, 1], [1, 1])
                """);
    }

    public void ContainsAll_Generic_MultipleMissing_PreservesFirstSeenOrderAndMultiplicity()
    {
        // Walk: 3 ✓, 1 ✓, 1 → missing(1), 4 → missing(4), 1 → missing(1)  =>  [1, 4, 1]
        Action action = () => Assert.ContainsAll([3, 1, 1, 4, 1], [1, 2, 3]);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to contain all specified items.

                missing:    [1, 4, 1]
                expected:   [3, 1, 1, 4, 1]
                collection: [1, 2, 3]

                Assert.ContainsAll([3, 1, 1, 4, 1], [1, 2, 3])
                """);
    }

    public void ContainsAll_Generic_ExcessInMiddleOfMatchingRun_ReportsOnlyExcess()
    {
        Action action = () => Assert.ContainsAll([1, 2, 1, 3], [1, 2, 3]);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to contain all specified items.

                missing:    [1]
                expected:   [1, 2, 1, 3]
                collection: [1, 2, 3]

                Assert.ContainsAll([1, 2, 1, 3], [1, 2, 3])
                """);
    }

    public void DoesNotContainAll_Generic_DuplicatesExceedCollectionMultiplicity_ShouldPass()
        => Assert.DoesNotContainAll([1, 1], [1]);

    public void DoesNotContainAll_Generic_DuplicatesWithinCollectionMultiplicity_ShouldFail()
    {
        Action action = () => Assert.DoesNotContainAll([1, 1], [1, 1, 2]);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to not contain all specified items.

                notExpected: [1, 1]
                collection:  [1, 1, 2]

                Assert.DoesNotContainAll([1, 1], [1, 1, 2])
                """);
    }

    public void ContainsAll_Generic_MissingElement_ShouldFail()
    {
        Action action = () => Assert.ContainsAll([1, 2, 3], [1, 2]);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to contain all specified items.

                missing:    [3]
                expected:   [1, 2, 3]
                collection: [1, 2]

                Assert.ContainsAll([1, 2, 3], [1, 2])
                """);
    }

    public void ContainsAll_Generic_NullInExpectedButNotInCollection_ShouldFail()
    {
        Action action = () => Assert.ContainsAll(["a", null], ["a"]);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to contain all specified items.

                missing:    [null]
                expected:   ["a", null]
                collection: ["a"]

                Assert.ContainsAll(["a", null], ["a"])
                """);
    }

    public void ContainsAll_Generic_StringMessage_MissingElement_ShouldFail()
    {
        Action action = () => Assert.ContainsAll([1, 2, 3], [1, 2], "User-provided message");
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to contain all specified items.
                User-provided message

                missing:    [3]
                expected:   [1, 2, 3]
                collection: [1, 2]

                Assert.ContainsAll([1, 2, 3], [1, 2])
                """);
    }

    public void ContainsAll_Generic_WithComparer_AllPresent_ShouldPass()
        => Assert.ContainsAll(["A"], ["a", "b"], new CaseInsensitiveStringComparer());

    public void ContainsAll_Generic_WithComparer_MissingElement_ShouldFail()
    {
        Action action = () => Assert.ContainsAll(["A", "C"], ["a", "b"], new CaseInsensitiveStringComparer());
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to contain all specified items.

                missing:    ["C"]
                expected:   ["A", "C"]
                collection: ["a", "b"]
                comparer:   CaseInsensitiveStringComparer

                Assert.ContainsAll(["A", "C"], ["a", "b"], <comparer>)
                """);
    }

    public void ContainsAll_Generic_NullExpected_ShouldFail()
    {
        IEnumerable<int> collection = [1, 2];
        Action action = () => Assert.ContainsAll(null, collection);
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.ContainsAll failed. The parameter 'expected' is invalid. The value cannot be null.");
    }

    public void ContainsAll_Generic_NullCollection_ShouldFail()
    {
        IEnumerable<int>? collection = null;
        Action action = () => Assert.ContainsAll([1, 2], collection);
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.ContainsAll failed. The parameter 'collection' is invalid. The value cannot be null.");
    }

    public void ContainsAll_Generic_NullComparer_ShouldFail()
    {
        Action action = () => Assert.ContainsAll([1], [1], (IEqualityComparer<int>?)null);
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.ContainsAll failed. The parameter 'comparer' is invalid. The value cannot be null.");
    }

    public void ContainsAll_NonGeneric_AllPresent_ShouldPass()
        => Assert.ContainsAll(new ArrayList { 1, 2 }, new ArrayList { 1, 2, 3 });

    public void ContainsAll_NonGeneric_WithComparer_AllPresent_ShouldPass()
        => Assert.ContainsAll(new ArrayList { "A", "b" }, new ArrayList { "a", "B", "c" }, new CaseInsensitiveStringComparer());

    public void DoesNotContainAll_NonGeneric_WithComparer_MissingElement_ShouldPass()
        => Assert.DoesNotContainAll(new ArrayList { "A", "C" }, new ArrayList { "a", "b" }, new CaseInsensitiveStringComparer());

    public void ContainsAll_NonGeneric_MissingElement_ShouldFail()
    {
        IEnumerable expected = new ArrayList { 1, 2, 3 };
        IEnumerable collection = new ArrayList { 1, 2 };
        Action action = () => Assert.ContainsAll(expected, collection);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to contain all specified items.

                missing:    [3]
                expected:   [1, 2, 3]
                collection: [1, 2]

                Assert.ContainsAll(expected, collection)
                """);
    }

    public void ContainsAll_NonGeneric_WithComparer_MissingElement_ShouldFail()
    {
        IEnumerable expected = new ArrayList { "A", "C" };
        IEnumerable collection = new ArrayList { "a", "b" };
        Action action = () => Assert.ContainsAll(expected, collection, new CaseInsensitiveStringComparer());
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to contain all specified items.

                missing:    ["C"]
                expected:   ["A", "C"]
                collection: ["a", "b"]
                comparer:   CaseInsensitiveStringComparer

                Assert.ContainsAll(expected, collection, <comparer>)
                """);
    }

    public void DoesNotContainAll_Generic_MissingElement_ShouldPass()
        => Assert.DoesNotContainAll([1, 2, 3], [1, 2]);

    public void DoesNotContainAll_Generic_AllPresent_ShouldFail()
    {
        Action action = () => Assert.DoesNotContainAll([1, 2], [1, 2, 3]);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to not contain all specified items.

                notExpected: [1, 2]
                collection:  [1, 2, 3]

                Assert.DoesNotContainAll([1, 2], [1, 2, 3])
                """);
    }

    public void DoesNotContainAll_Generic_StringMessage_AllPresent_ShouldFail()
    {
        Action action = () => Assert.DoesNotContainAll([1], [1, 2], "User-provided message");
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to not contain all specified items.
                User-provided message

                notExpected: [1]
                collection:  [1, 2]

                Assert.DoesNotContainAll([1], [1, 2])
                """);
    }

    public void DoesNotContainAll_Generic_WithComparer_MissingElement_ShouldPass()
        => Assert.DoesNotContainAll(["A", "C"], ["a", "b"], new CaseInsensitiveStringComparer());

    public void DoesNotContainAll_Generic_WithComparer_AllPresent_ShouldFail()
    {
        Action action = () => Assert.DoesNotContainAll(["A"], ["a", "b"], new CaseInsensitiveStringComparer());
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to not contain all specified items.

                notExpected: ["A"]
                collection:  ["a", "b"]
                comparer:    CaseInsensitiveStringComparer

                Assert.DoesNotContainAll(["A"], ["a", "b"], <comparer>)
                """);
    }

    public void DoesNotContainAll_Generic_NullExpected_ShouldFail()
    {
        IEnumerable<int> collection = [1, 2];
        Action action = () => Assert.DoesNotContainAll(null, collection);
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.DoesNotContainAll failed. The parameter 'notExpected' is invalid. The value cannot be null.");
    }

    public void DoesNotContainAll_Generic_NullCollection_ShouldFail()
    {
        IEnumerable<int>? collection = null;
        Action action = () => Assert.DoesNotContainAll([1, 2], collection);
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.DoesNotContainAll failed. The parameter 'collection' is invalid. The value cannot be null.");
    }

    public void DoesNotContainAll_Generic_NullComparer_ShouldFail()
    {
        Action action = () => Assert.DoesNotContainAll([1], [1], (IEqualityComparer<int>?)null);
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.DoesNotContainAll failed. The parameter 'comparer' is invalid. The value cannot be null.");
    }

    public void ContainsAll_NonGeneric_NullExpected_ShouldFail()
    {
        Action action = () => Assert.ContainsAll(null, new ArrayList { 1, 2 });
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.ContainsAll failed. The parameter 'expected' is invalid. The value cannot be null.");
    }

    public void ContainsAll_NonGeneric_NullCollection_ShouldFail()
    {
        Action action = () => Assert.ContainsAll(new ArrayList { 1 }, null);
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.ContainsAll failed. The parameter 'collection' is invalid. The value cannot be null.");
    }

    public void ContainsAll_NonGeneric_NullComparer_ShouldFail()
    {
        Action action = () => Assert.ContainsAll(new ArrayList { 1 }, new ArrayList { 1 }, (IEqualityComparer?)null);
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.ContainsAll failed. The parameter 'comparer' is invalid. The value cannot be null.");
    }

    public void DoesNotContainAll_NonGeneric_NullExpected_ShouldFail()
    {
        Action action = () => Assert.DoesNotContainAll(null, new ArrayList { 1, 2 });
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.DoesNotContainAll failed. The parameter 'notExpected' is invalid. The value cannot be null.");
    }

    public void DoesNotContainAll_NonGeneric_NullCollection_ShouldFail()
    {
        Action action = () => Assert.DoesNotContainAll(new ArrayList { 1 }, null);
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.DoesNotContainAll failed. The parameter 'collection' is invalid. The value cannot be null.");
    }

    public void DoesNotContainAll_NonGeneric_NullComparer_ShouldFail()
    {
        Action action = () => Assert.DoesNotContainAll(new ArrayList { 1 }, new ArrayList { 1 }, (IEqualityComparer?)null);
        action.Should().Throw<AssertFailedException>()
            .WithMessage("Assert.DoesNotContainAll failed. The parameter 'comparer' is invalid. The value cannot be null.");
    }

    public void DoesNotContainAll_Generic_BothEmpty_ShouldFail()
    {
        Action action = () => Assert.DoesNotContainAll<int>([], []);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to not contain all specified items.

                notExpected: []
                collection:  []

                Assert.DoesNotContainAll([], [])
                """);
    }

    public void DoesNotContainAll_Generic_EmptyExpected_ShouldFail()
    {
        Action action = () => Assert.DoesNotContainAll([], [1]);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to not contain all specified items.

                notExpected: []
                collection:  [1]

                Assert.DoesNotContainAll([], [1])
                """);
    }

    public void DoesNotContainAll_NonGeneric_WithComparer_AllPresent_ShouldFail()
    {
        IEnumerable notExpected = new ArrayList { "A" };
        IEnumerable collection = new ArrayList { "a", "b" };
        Action action = () => Assert.DoesNotContainAll(notExpected, collection, new CaseInsensitiveStringComparer());
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to not contain all specified items.

                notExpected: ["A"]
                collection:  ["a", "b"]
                comparer:    CaseInsensitiveStringComparer

                Assert.DoesNotContainAll(notExpected, collection, <comparer>)
                """);
    }

    public void ContainsAll_Generic_NullInCollectionButNotInExpected_ShouldPass()
        => Assert.ContainsAll(["a"], ["a", null]);

    public void ContainsAll_Generic_WithComparer_NoCallerArgumentExpression_ShouldRenderPlaceholderCallSite()
    {
        // Simulate a caller (e.g. another language or a wrapper) that does not propagate
        // [CallerArgumentExpression] values. The call site must still render with placeholders,
        // including a "<comparer>" placeholder, so the failure shows which overload was invoked.
        Action action = () => Assert.ContainsAll(
            ["A", "C"],
            ["a", "b"],
            new CaseInsensitiveStringComparer(),
            message: null,
            expectedExpression: string.Empty,
            collectionExpression: string.Empty);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to contain all specified items.

                missing:    ["C"]
                expected:   ["A", "C"]
                collection: ["a", "b"]
                comparer:   CaseInsensitiveStringComparer

                Assert.ContainsAll(<expected>, <collection>, <comparer>)
                """);
    }

    public void DoesNotContainAll_Generic_WithComparer_NoCallerArgumentExpression_ShouldRenderPlaceholderCallSite()
    {
        Action action = () => Assert.DoesNotContainAll(
            ["A"],
            ["a", "b"],
            new CaseInsensitiveStringComparer(),
            message: null,
            notExpectedExpression: string.Empty,
            collectionExpression: string.Empty);
        action.Should().Throw<AssertFailedException>()
            .WithMessage(
                """
                Assertion failed. Expected collection to not contain all specified items.

                notExpected: ["A"]
                collection:  ["a", "b"]
                comparer:    CaseInsensitiveStringComparer

                Assert.DoesNotContainAll(<notExpected>, <collection>, <comparer>)
                """);
    }

    public void ContainsAll_AssertFailedException_PopulatesExpectedAndActual()
    {
        Action action = () => Assert.ContainsAll([1, 2, 3], [1, 2]);
        AssertFailedException ex = action.Should().Throw<AssertFailedException>().Which;
        ex.ExpectedText.Should().Be("[1, 2, 3]");
        ex.ActualText.Should().Be("[1, 2]");
    }

    public void DoesNotContainAll_AssertFailedException_PopulatesActualOnly()
    {
        Action action = () => Assert.DoesNotContainAll([1], [1, 2]);
        AssertFailedException ex = action.Should().Throw<AssertFailedException>().Which;
        ex.ExpectedText.Should().BeNull();
        ex.ActualText.Should().Be("[1, 2]");
    }

    public void ContainsAll_CaseInsensitiveStringComparer_NonGenericEquals_WithNonStringValues_ShouldReturnFalse()
    {
        IEqualityComparer comparer = new CaseInsensitiveStringComparer();
        object left = new();
        object right = new();

        comparer.Equals(null, null).Should().BeTrue();
        comparer.Equals(null, "a").Should().BeFalse();
        comparer.Equals(left, right).Should().BeFalse();
        comparer.Equals("A", "a").Should().BeTrue();
    }

    public void ContainsAll_CaseInsensitiveStringComparer_NonGenericGetHashCode_WithNonStringValue_ShouldUseRuntimeHashCode()
    {
        IEqualityComparer comparer = new CaseInsensitiveStringComparer();
        object value = new();

        comparer.GetHashCode(value).Should().Be(RuntimeHelpers.GetHashCode(value));
    }
}
