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

using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.WebEncoders.Testing;

namespace Microsoft.AspNetCore.Mvc.Core.Rendering;

public class TagBuilderTest
{
    public static TheoryData<TagRenderMode, string> RenderingTestingData
    {
        get
        {
            return new TheoryData<TagRenderMode, string>
                {
                    { TagRenderMode.StartTag, "<p>" },
                    { TagRenderMode.SelfClosing, "<p />" },
                    { TagRenderMode.Normal, "<p></p>" }
                };
        }
    }

    [Theory]
    [InlineData(false, "Hello", "World")]
    [InlineData(true, "hello", "something else")]
    public void MergeAttribute_IgnoresCase(bool replaceExisting, string expectedKey, string expectedValue)
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");
        tagBuilder.Attributes.Add("Hello", "World");

        // Act
        tagBuilder.MergeAttribute("hello", "something else", replaceExisting);

        // Assert
        var attribute = Assert.Single(tagBuilder.Attributes);
        Assert.Equal(new KeyValuePair<string, string>(expectedKey, expectedValue), attribute);
    }

    [Fact]
    public void AddCssClass_IgnoresCase()
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");
        tagBuilder.Attributes.Add("ClaSs", "btn");

        // Act
        tagBuilder.AddCssClass("btn-success");

        // Assert
        var attribute = Assert.Single(tagBuilder.Attributes);
        Assert.Equal(new KeyValuePair<string, string>("class", "btn btn-success"), attribute);
    }

    [Fact]
    public void GenerateId_IgnoresCase()
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");
        tagBuilder.Attributes.Add("ID", "something");

        // Act
        tagBuilder.GenerateId("else", invalidCharReplacement: "-");

        // Assert
        var attribute = Assert.Single(tagBuilder.Attributes);
        Assert.Equal(new KeyValuePair<string, string>("ID", "something"), attribute);
    }

    [Theory]
    [MemberData(nameof(RenderingTestingData))]
    public void WriteTo_IgnoresIdAttributeCase(TagRenderMode renderingMode, string expectedOutput)
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");
        // An empty value id attribute should not be rendered via ToString.
        tagBuilder.Attributes.Add("ID", string.Empty);
        tagBuilder.TagRenderMode = renderingMode;

        // Act
        using (var writer = new StringWriter())
        {
            tagBuilder.WriteTo(writer, new HtmlTestEncoder());

            // Assert
            Assert.Equal(expectedOutput, writer.ToString());
        }
    }

    [Theory]
    [InlineData(null, "")]
    [InlineData("", "")]
    [InlineData("a", "a")]
    [InlineData("0", "z")]
    [InlineData("-", "z")]
    [InlineData(",", "z")]
    [InlineData(",.", "z-")]
    [InlineData("a\uD83D\uDE0A", "a--")]
    [InlineData("a\uD83D\uDE0A.", "a---")]
    [InlineData("\uD83D\uDE0A", "z-")]
    [InlineData("\uD83D\uDE0A.", "z--")]
    [InlineData(",a", "za")]
    [InlineData("a,", "a-")]
    [InlineData("a0,", "a0-")]
    [InlineData("a,0,", "a-0-")]
    [InlineData("a,0", "a-0")]
    [InlineData("00Hello,World", "z0Hello-World")]
    [InlineData(",,Hello,,World,,", "z-Hello--World--")]
    [InlineData("-_:Hello-_:Hello-_:", "z_:Hello-_:Hello-_:")]
    [InlineData("HelloWorld", "HelloWorld")]
    [InlineData("�HelloWorld", "zHelloWorld")]
    [InlineData("Hello�World", "Hello-World")]
    public void CreateSanitizedIdCreatesId(string input, string output)
    {
        // Arrange
        var result = TagBuilder.CreateSanitizedId(input, "-");

        // Assert
        Assert.Equal(output, result);
    }

    [Fact]
    public void CreateSanitizedIdCreatesIdReplacesAllInvalidCharacters()
    {
        foreach (char c in Enumerable.Range(char.MinValue, char.MaxValue))
        {
            var result = TagBuilder.CreateSanitizedId($"a{c}", "z");
            if (char.IsAsciiLetterOrDigit(c) || c == '-' || c == '_' || c == ':')
            {
                Assert.Equal($"a{c}", result);
            }
            else
            {
                Assert.Equal("az", result);
            }
        }
    }

    [Theory]
    [InlineData("attribute", "value", "<p attribute=\"HtmlEncode[[value]]\"></p>")]
    [InlineData("attribute", null, "<p attribute=\"\"></p>")]
    [InlineData("attribute", "", "<p attribute=\"\"></p>")]
    public void WriteTo_WriteEmptyAttribute_WhenValueIsNullOrEmpty(
        string attributeKey,
        string attributeValue,
        string expectedOutput)
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");

        // Act
        tagBuilder.Attributes.Add(attributeKey, attributeValue);

        // Assert
        using (var writer = new StringWriter())
        {
            tagBuilder.WriteTo(writer, new HtmlTestEncoder());
            Assert.Equal(expectedOutput, writer.ToString());
        }
    }

    [Fact]
    public void WriteTo_IncludesInnerHtml()
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");
        tagBuilder.InnerHtml.AppendHtml("<span>Hello</span>");
        tagBuilder.InnerHtml.Append(", World!");

        // Act
        using (var writer = new StringWriter())
        {
            tagBuilder.WriteTo(writer, new HtmlTestEncoder());

            // Assert
            Assert.Equal("<p><span>Hello</span>HtmlEncode[[, World!]]</p>", writer.ToString());
        }

        Assert.True(tagBuilder.HasInnerHtml);
    }

    [Fact]
    public void ReadingInnerHtml_LeavesHasInnerHtmlFalse()
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");

        // Act
        var innerHtml = tagBuilder.InnerHtml;

        // Assert
        Assert.False(tagBuilder.HasInnerHtml);
        Assert.NotNull(innerHtml);
    }

    [Fact]
    public void RenderStartTag_RendersExpectedStartTag()
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");

        // Act
        var tag = tagBuilder.RenderStartTag();

        // Assert
        Assert.Equal("<p>", HtmlContentUtilities.HtmlContentToString(tag));
    }

    [Fact]
    public void RenderStartTag_RendersExpectedStartTag_TagBuilderRendersAsExpected()
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");
        tagBuilder.TagRenderMode = TagRenderMode.EndTag;

        // Act
        var tag = tagBuilder.RenderStartTag();

        // Assert
        Assert.Equal("<p>", HtmlContentUtilities.HtmlContentToString(tag));
        Assert.Equal("</p>", HtmlContentUtilities.HtmlContentToString(tagBuilder));
    }

    [Fact]
    public void RenderEndTag_RendersExpectedEndTag()
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");

        // Act
        var tag = tagBuilder.RenderEndTag();

        // Assert
        Assert.Equal("</p>", HtmlContentUtilities.HtmlContentToString(tag));
    }

    [Fact]
    public void RenderEndTag_RendersExpectedEndTag_TagBuilderRendersAsExpected()
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");
        tagBuilder.TagRenderMode = TagRenderMode.Normal;

        // Act
        var tag = tagBuilder.RenderEndTag();

        // Assert
        Assert.Equal("</p>", HtmlContentUtilities.HtmlContentToString(tag));
        Assert.Equal("<p></p>", HtmlContentUtilities.HtmlContentToString(tagBuilder));
    }

    [Fact]
    public void RenderSelfClosingTag_RendersExpectedSelfClosingTag()
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");

        // Act
        var tag = tagBuilder.RenderSelfClosingTag();

        // Assert
        Assert.Equal("<p />", HtmlContentUtilities.HtmlContentToString(tag));
    }

    [Fact]
    public void RenderBody_RendersExpectedBody()
    {
        // Arrange
        var tagBuilder = new TagBuilder("p");
        tagBuilder.InnerHtml.AppendHtml("<span>Hello</span>");

        // Act
        var tag = tagBuilder.RenderBody();

        // Assert
        Assert.Equal("<span>Hello</span>", HtmlContentUtilities.HtmlContentToString(tag));
    }

    [Fact]
    public void Constructor_Copy_CopiesTagRenderMode()
    {
        // Arrange
        var originalTagBuilder = new TagBuilder("p");
        originalTagBuilder.TagRenderMode = TagRenderMode.SelfClosing;

        // Act
        var clonedTagBuilder = new TagBuilder(originalTagBuilder);

        // Assert
        Assert.Equal(originalTagBuilder.TagRenderMode, clonedTagBuilder.TagRenderMode);
    }

    [Fact]
    public void Constructor_Copy_DoesShallowCopyOfInnerHtml()
    {
        // Arrange
        var originalTagBuilder = new TagBuilder("p");
        originalTagBuilder.InnerHtml.AppendHtml("<span>Hello</span>");

        // Act
        var clonedTagBuilder = new TagBuilder(originalTagBuilder);

        // Assert
        Assert.NotEqual(originalTagBuilder.InnerHtml, clonedTagBuilder.InnerHtml);
        Assert.Equal(HtmlContentUtilities.HtmlContentToString(originalTagBuilder.RenderBody()), HtmlContentUtilities.HtmlContentToString(clonedTagBuilder.RenderBody()));
    }

    [Fact]
    public void Constructor_Copy_DoesShallowCopyOfAttributes()
    {
        // Arrange
        var originalTagBuilder = new TagBuilder("p");
        originalTagBuilder.AddCssClass("class1");

        // Act
        var clonedTagBuilder = new TagBuilder(originalTagBuilder);

        // Assert
        Assert.NotSame(originalTagBuilder.Attributes, clonedTagBuilder.Attributes);
        Assert.Equal(originalTagBuilder.Attributes, clonedTagBuilder.Attributes);
    }

    [Fact]
    public void Constructor_Copy_CopiesTagName()
    {
        // Arrange
        var originalTagBuilder = new TagBuilder("p");

        // Act

        var clonedTagBuilder = new TagBuilder(originalTagBuilder);

        // Assert
        Assert.Equal(originalTagBuilder.TagName, clonedTagBuilder.TagName);
    }
}
