// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace Microsoft.AspNetCore.Routing.Patterns
{
    public class DefaultRoutePatternTransformerTest
    {
        public DefaultRoutePatternTransformerTest()
        {
            var services = new ServiceCollection();
            services.AddRouting();
            services.AddOptions();
            Transformer = services.BuildServiceProvider().GetRequiredService<RoutePatternTransformer>();
        }

        public RoutePatternTransformer Transformer { get; }

        [Fact]
        public void SubstituteRequiredValues_CanAcceptNullForAnyKey()
        {
            // Arrange
            var template = "{controller=Home}/{action=Index}/{id?}";
            var defaults = new { };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { a = (string)null, b = "", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("a", null), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("b", string.Empty), kvp));
        }

        [Fact]
        public void SubstituteRequiredValues_RejectsNullForParameter()
        {
            // Arrange
            var template = "{controller=Home}/{action=Index}/{id?}";
            var defaults = new { };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = string.Empty, };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Null(actual);
        }

        [Fact]
        public void SubstituteRequiredValues_AllowRequiredValueAnyForParameter()
        {
            // Arrange
            var template = "{controller=Home}/{action=Index}/{id?}";
            var defaults = new { };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = RoutePattern.RequiredValueAny, };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.Defaults.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp)); // default is preserved

            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("controller", RoutePattern.RequiredValueAny), kvp));
        }

        [Fact]
        public void SubstituteRequiredValues_RejectsNullForOutOfLineDefault()
        {
            // Arrange
            var template = "{controller=Home}/{action=Index}/{id?}";
            var defaults = new { area = "Admin" };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { area = string.Empty, };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Null(actual);
        }

        [Fact]
        public void SubstituteRequiredValues_RejectsRequiredValueAnyForOutOfLineDefault()
        {
            // Arrange
            var template = "{controller=Home}/{action=Index}/{id?}";
            var defaults = new { area = RoutePattern.RequiredValueAny };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { area = string.Empty, };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Null(actual);
        }

        [Fact]
        public void SubstituteRequiredValues_CanAcceptValueForParameter()
        {
            // Arrange
            var template = "{controller}/{action}/{id?}";
            var defaults = new { };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = "Home", action = "Index", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
        }

        [Fact]
        public void SubstituteRequiredValues_CanAcceptValueForParameter_WithSameDefault()
        {
            // Arrange
            var template = "{controller=Home}/{action=Index}/{id?}";
            var defaults = new { };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = "Home", action = "Index", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));

            // We should not need to rewrite anything in this case.
            Assert.Same(actual.Defaults, original.Defaults);
            Assert.Same(actual.Parameters, original.Parameters);
            Assert.Same(actual.PathSegments, original.PathSegments);
        }

        [Fact]
        public void SubstituteRequiredValues_CanAcceptValueForParameter_WithDifferentDefault()
        {
            // Arrange
            var template = "{controller=Blog}/{action=ReadPost}/{id?}";
            var defaults = new { area = "Admin", };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { area = "Admin", controller = "Home", action = "Index", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));

            // We should not need to rewrite anything in this case.
            Assert.NotSame(actual.Defaults, original.Defaults);
            Assert.NotSame(actual.Parameters, original.Parameters);
            Assert.NotSame(actual.PathSegments, original.PathSegments);

            // other defaults were wiped out
            Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), Assert.Single(actual.Defaults));
            Assert.Null(actual.GetParameter("controller").Default);
            Assert.False(actual.Defaults.ContainsKey("controller"));
            Assert.Null(actual.GetParameter("action").Default);
            Assert.False(actual.Defaults.ContainsKey("action"));
        }

        [Fact]
        public void SubstituteRequiredValues_CanAcceptValueForParameter_WithMatchingConstraint()
        {
            // Arrange
            var template = "{controller}/{action}/{id?}";
            var defaults = new { };
            var policies = new { controller = "Home", action = new RegexRouteConstraint("Index"), };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = "Home", action = "Index", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
        }

        [Fact]
        public void SubstituteRequiredValues_CanRejectValueForParameter_WithNonMatchingConstraint()
        {
            // Arrange
            var template = "{controller}/{action}/{id?}";
            var defaults = new { };
            var policies = new { controller = "Home", action = new RegexRouteConstraint("Index"), };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = "Blog", action = "Index", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Null(actual);
        }

        [Fact]
        public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue()
        {
            // Arrange
            var template = "Home/Index/{id?}";
            var defaults = new { controller = "Home", action = "Index", };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = "Home", action = "Index", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
        }

        [Fact]
        public void SubstituteRequiredValues_CanRejectValueForDefault_WithDifferentValue()
        {
            // Arrange
            var template = "Home/Index/{id?}";
            var defaults = new { controller = "Home", action = "Index", };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = "Blog", action = "Index", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Null(actual);
        }

        [Fact]
        public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue_Null()
        {
            // Arrange
            var template = "Home/Index/{id?}";
            var defaults = new { controller = (string)null, action = "", };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = string.Empty, action = (string)null, };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("action", null), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("controller", ""), kvp));
        }

        [Fact]
        public void SubstituteRequiredValues_CanAcceptValueForDefault_WithSameValue_WithMatchingConstraint()
        {
            // Arrange
            var template = "Home/Index/{id?}";
            var defaults = new { controller = "Home", action = "Index", };
            var policies = new { controller = "Home", };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = "Home", action = "Index", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
        }

        [Fact]
        public void SubstituteRequiredValues_CanRejectValueForDefault_WithSameValue_WithNonMatchingConstraint()
        {
            // Arrange
            var template = "Home/Index/{id?}";
            var defaults = new { controller = "Home", action = "Index", };
            var policies = new { controller = "Home", };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { controller = "Home", action = "Index", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
        }

        [Fact]
        public void SubstituteRequiredValues_CanMergeExistingRequiredValues()
        {
            // Arrange
            var template = "Home/Index/{id?}";
            var defaults = new { area = "Admin", controller = "Home", action = "Index", };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies, new { area = "Admin", controller = "Home", });

            var requiredValues = new { controller = "Home", action = "Index", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Collection(
                actual.RequiredValues.OrderBy(kvp => kvp.Key),
                kvp => Assert.Equal(new KeyValuePair<string, object>("action", "Index"), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("area", "Admin"), kvp),
                kvp => Assert.Equal(new KeyValuePair<string, object>("controller", "Home"), kvp));
        }

        [Fact]
        public void SubstituteRequiredValues_NullRequiredValueParameter_Fail()
        {
            // Arrange
            var template = "PageRoute/Attribute/{page}";
            var defaults = new { area = (string)null, page = (string)null, controller = "Home", action = "Index", };
            var policies = new { };

            var original = RoutePatternFactory.Parse(template, defaults, policies);

            var requiredValues = new { area = (string)null, page = (string)null, controller = "Home", action = "Index", };

            // Act
            var actual = Transformer.SubstituteRequiredValues(original, requiredValues);

            // Assert
            Assert.Null(actual);
        }
    }
}
