// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Xml.Linq;
using Xunit.Sdk;

namespace Microsoft.AspNetCore.Mvc.Formatters.Xml;

/// <summary>
/// Xunit assertions related to Xml content.
/// </summary>
public static class XmlAssert
{
    /// <summary>
    /// Compares two xml strings ignoring an element's attribute order.
    /// </summary>
    /// <param name="expectedXml">Expected xml string.</param>
    /// <param name="actualXml">Actual xml string.</param>
    public static void Equal(string expectedXml, string actualXml)
    {
        var sortedExpectedXDocument = SortAttributes(XDocument.Parse(expectedXml));
        var sortedActualXDocument = SortAttributes(XDocument.Parse(actualXml));

        // Since XNode's DeepEquals does not check for presence of xml declaration,
        // check it explicitly
        bool areEqual = EqualDeclarations(sortedExpectedXDocument.Declaration, sortedActualXDocument.Declaration);

        areEqual = areEqual && XNode.DeepEquals(sortedExpectedXDocument, sortedActualXDocument);

        if (!areEqual)
        {
            throw new EqualException(
                sortedExpectedXDocument.ToString(SaveOptions.DisableFormatting),
                sortedActualXDocument.ToString(SaveOptions.DisableFormatting));
        }
    }

    private static bool EqualDeclarations(XDeclaration expected, XDeclaration actual)
    {
        if (expected == null && actual == null)
        {
            return true;
        }

        if (expected == null || actual == null)
        {
            return false;
        }

        // Note that this ignores 'Standalone' property comparison.
        return string.Equals(expected.Version, actual.Version, StringComparison.OrdinalIgnoreCase)
            && string.Equals(expected.Encoding, actual.Encoding, StringComparison.OrdinalIgnoreCase);
    }

    private static XDocument SortAttributes(XDocument document)
    {
        return new XDocument(
            document.Declaration,
            SortAttributes(document.Root));
    }

    private static XNode SortAttributes(XNode node)
    {
        XElement element = node as XElement;
        if (element == null)
        {
            return node;
        }

        return new XElement(
            element.Name,
            element.Attributes().OrderBy(a => a.Name.ToString()),
            element.Nodes().Select(child => SortAttributes(child)));
    }
}
