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

using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Rewrite.IISUrlRewrite;
using Microsoft.AspNetCore.Rewrite.UrlActions;
using Microsoft.AspNetCore.Rewrite.UrlMatches;

namespace Microsoft.AspNetCore.Rewrite.Tests.UrlRewrite;

public class FileParserTests
{
    [Fact]
    public void RuleParse_ParseTypicalRule()
    {
        // arrange
        var xml = @"<rewrite>
                            <rules>
                                <rule name=""Rewrite to article.aspx"">
                                    <match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" />
                                    <action type=""Rewrite"" url =""article.aspx?id={R:1}&amp;title={R:2}"" />
                                </rule>
                            </rules>
                        </rewrite>";

        var expected = new List<IISUrlRewriteRule>();
        expected.Add(CreateTestRule(new ConditionCollection(),
            url: "^article/([0-9]+)/([_0-9a-z-]+)",
            name: "Rewrite to article.aspx",
            actionType: ActionType.Rewrite,
            pattern: "article.aspx?id={R:1}&amp;title={R:2}"));

        // act
        var res = new UrlRewriteFileParser().Parse(new StringReader(xml), false);

        // assert
        AssertUrlRewriteRuleEquality(expected, res);
    }

    [Fact]
    public void RuleParse_ParseSingleRuleWithSingleCondition()
    {
        // arrange
        var xml = @"<rewrite>
                            <rules>
                                <rule name=""Rewrite to article.aspx"">
                                    <match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" />
                                    <conditions>
                                        <add input=""{HTTPS}"" pattern=""^OFF$"" />
                                    </conditions>
                                    <action type=""Rewrite"" url =""article.aspx?id={R:1}&amp;title={R:2}"" />
                                </rule>
                            </rules>
                        </rewrite>";

        var condList = new ConditionCollection();
        condList.Add(new Condition(new InputParser().ParseInputString("{HTTPS}"), new RegexMatch(new Regex("^OFF$"), false)));

        var expected = new List<IISUrlRewriteRule>();
        expected.Add(CreateTestRule(condList,
            url: "^article/([0-9]+)/([_0-9a-z-]+)",
            name: "Rewrite to article.aspx",
            actionType: ActionType.Rewrite,
            pattern: "article.aspx?id={R:1}&amp;title={R:2}"));

        // act
        var res = new UrlRewriteFileParser().Parse(new StringReader(xml), false);

        // assert
        AssertUrlRewriteRuleEquality(expected, res);
    }

    [Fact]
    public void RuleParse_ParseMultipleRules()
    {
        // arrange
        var xml = @"<rewrite>
                            <rules>
                                <rule name=""Rewrite to article.aspx"">
                                    <match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" />
                                    <conditions>
                                        <add input=""{HTTPS}"" pattern=""^OFF$"" />
                                    </conditions>
                                    <action type=""Rewrite"" url =""article.aspx?id={R:1}&amp;title={R:2}"" />
                                </rule>
                                <rule name=""Rewrite to another article.aspx"">
                                    <match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" />
                                    <conditions>
                                        <add input=""{HTTPS}"" pattern=""^OFF$"" />
                                    </conditions>
                                    <action type=""Rewrite"" url =""article.aspx?id={R:1}&amp;title={R:2}"" />
                                </rule>
                            </rules>
                        </rewrite>";

        var condList = new ConditionCollection();
        condList.Add(new Condition(new InputParser().ParseInputString("{HTTPS}"), new RegexMatch(new Regex("^OFF$"), false)));

        var expected = new List<IISUrlRewriteRule>();
        expected.Add(CreateTestRule(condList,
            url: "^article/([0-9]+)/([_0-9a-z-]+)",
            name: "Rewrite to article.aspx",
            actionType: ActionType.Rewrite,
            pattern: "article.aspx?id={R:1}&amp;title={R:2}"));
        expected.Add(CreateTestRule(condList,
            url: "^article/([0-9]+)/([_0-9a-z-]+)",
            name: "Rewrite to another article.aspx",
            actionType: ActionType.Rewrite,
            pattern: "article.aspx?id={R:1}&amp;title={R:2}"));

        // act
        var res = new UrlRewriteFileParser().Parse(new StringReader(xml), false);

        // assert
        AssertUrlRewriteRuleEquality(expected, res);
    }

    [Fact]
    public void Should_parse_global_rules()
    {
        // arrange
        var xml = @"<rewrite>
                            <globalRules>
                                <rule name=""httpsOnly"" patternSyntax=""ECMAScript"" stopProcessing=""true"">
                                    <match url="".*"" />
                                    <conditions logicalGrouping=""MatchAll"" trackAllCaptures=""false"">
                                        <add input=""{HTTPS}"" pattern=""off"" />
                                    </conditions>
                                    <action type=""Redirect"" url=""https://{HTTP_HOST}{REQUEST_URI}"" />
                                </rule>
                            </globalRules>
                            <rules>
                                <rule name=""Rewrite to article.aspx"">
                                    <match url = ""^article/([0-9]+)/([_0-9a-z-]+)"" />
                                    <action type=""Rewrite"" url =""article.aspx?id={R:1}&amp;title={R:2}"" />
                                </rule>
                            </rules>
                        </rewrite>";

        // act
        var rules = new UrlRewriteFileParser().Parse(new StringReader(xml), false);

        // assert
        Assert.Equal(2, rules.Count);
        Assert.True(rules[0].Global);
        Assert.False(rules[1].Global);
    }

    [Fact]
    public void Should_skip_empty_conditions()
    {
        // arrange
        var xml = @"<rewrite>
                            <rules>
                                <rule name=""redirect-aspnet-mvc"" enabled=""true"" stopProcessing=""true"">
                                    <match url=""^aspnet/Mvc"" />
                                    <conditions logicalGrouping=""MatchAll"" trackAllCaptures=""false"" />
                                    <action type=""Redirect"" url=""https://github.com/dotnet/aspnetcore"" />
                                </rule>
                            </rules>
                        </rewrite>";

        // act
        var rules = new UrlRewriteFileParser().Parse(new StringReader(xml), false);

        // assert
        Assert.Null(rules[0].Conditions);
    }

    // Creates a rule with appropriate default values of the url rewrite rule.
    private static IISUrlRewriteRule CreateTestRule(ConditionCollection conditions,
        string name = "",
        bool enabled = true,
        PatternSyntax patternSyntax = PatternSyntax.ECMAScript,
        bool stopProcessing = false,
        string url = "",
        bool ignoreCase = true,
        bool negate = false,
        ActionType actionType = ActionType.None,
        string pattern = "",
        bool appendQueryString = false,
        bool rewrittenUrl = false,
        bool global = false,
        UriMatchPart uriMatchPart = UriMatchPart.Path,
        RedirectType redirectType = RedirectType.Permanent
        )
    {
        return new IISUrlRewriteRule(
            name,
            new RegexMatch(new Regex("^OFF$"), negate),
            conditions,
            new RewriteAction(RuleResult.ContinueRules, new InputParser().ParseInputString(url, uriMatchPart), queryStringAppend: false),
            global);
    }

    // TODO make rules comparable?
    private static void AssertUrlRewriteRuleEquality(IList<IISUrlRewriteRule> actual, IList<IISUrlRewriteRule> expected)
    {
        Assert.Equal(actual.Count, expected.Count);
        for (var i = 0; i < actual.Count; i++)
        {
            var r1 = actual[i];
            var r2 = expected[i];

            Assert.Equal(r1.Name, r2.Name);

            if (r1.Conditions == null)
            {
                Assert.Equal(0, r2.Conditions.Count);
            }
            else if (r2.Conditions == null)
            {
                Assert.Equal(0, r1.Conditions.Count);
            }
            else
            {
                Assert.Equal(r1.Conditions.Count, r2.Conditions.Count);
                for (var j = 0; j < r1.Conditions.Count; j++)
                {
                    var c1 = r1.Conditions[j];
                    var c2 = r2.Conditions[j];
                    Assert.Equal(c1.Input.PatternSegments.Count, c2.Input.PatternSegments.Count);
                }
            }

            Assert.Equal(r1.Action.GetType(), r2.Action.GetType());
            Assert.Equal(r1.InitialMatch.GetType(), r2.InitialMatch.GetType());
        }
    }
}
