// 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.Template;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;

namespace Microsoft.AspNetCore.Routing.Tree
{
    public class TreeRouteBuilderTest
    {
        [Fact]
        public void TreeRouter_BuildThrows_RoutesWithTheSameNameAndDifferentTemplates()
        {
            // Arrange
            var builder = CreateBuilder();

            var message = "Two or more routes named 'Get_Products' have different templates.";

            builder.MapOutbound(
                Mock.Of<IRouter>(),
                TemplateParser.Parse("api/Products"),
                new RouteValueDictionary(),
                "Get_Products",
                order: 0);

            builder.MapOutbound(
                Mock.Of<IRouter>(),
                TemplateParser.Parse("Products/Index"),
                new RouteValueDictionary(),
                "Get_Products",
                order: 0);

            // Act & Assert
            ExceptionAssert.ThrowsArgument(() =>
            {
                builder.Build();
            }, "linkGenerationEntries", message);
        }

        [Fact]
        public void TreeRouter_BuildDoesNotThrow_RoutesWithTheSameNameAndSameTemplates()
        {
            // Arrange
            var builder = CreateBuilder();

            builder.MapOutbound(
                Mock.Of<IRouter>(),
                TemplateParser.Parse("api/Products"),
                new RouteValueDictionary(),
                "Get_Products",
                order: 0);

            builder.MapOutbound(
                Mock.Of<IRouter>(),
                TemplateParser.Parse("api/products"),
                new RouteValueDictionary(),
                "Get_Products",
                order: 0);

            // Act & Assert (does not throw)
            builder.Build();
        }

        [Fact]
        public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithDefaultValues()
        {
            // Arrange
            var builder = CreateBuilder();

            builder.MapInbound(
                Mock.Of<IRouter>(),
                TemplateParser.Parse("a/{b=3}/c"),
                "Intermediate",
                order: 0);

            // Act
            var tree = builder.Build();

            // Assert
            Assert.NotNull(tree);
            Assert.NotNull(tree.MatchingTrees);
            var matchingTree = Assert.Single(tree.MatchingTrees);

            var firstSegment = Assert.Single(matchingTree.Root.Literals);
            Assert.Equal("a", firstSegment.Key);
            Assert.NotNull(firstSegment.Value.Parameters);

            var secondSegment = firstSegment.Value.Parameters;
            Assert.Empty(secondSegment.Matches);

            var thirdSegment = Assert.Single(secondSegment.Literals);
            Assert.Equal("c", thirdSegment.Key);
            Assert.Single(thirdSegment.Value.Matches);
        }

        [Fact]
        public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithMultipleIntermediateParametersWithDefaultOrOptionalValues()
        {
            // Arrange
            var builder = CreateBuilder();

            builder.MapInbound(
                Mock.Of<IRouter>(),
                TemplateParser.Parse("a/{b=3}/c/{d?}/e/{*f}"),
                "Intermediate",
                order: 0);

            // Act
            var tree = builder.Build();

            // Assert
            Assert.NotNull(tree);
            Assert.NotNull(tree.MatchingTrees);
            var matchingTree = Assert.Single(tree.MatchingTrees);

            var firstSegment = Assert.Single(matchingTree.Root.Literals);
            Assert.Equal("a", firstSegment.Key);
            Assert.NotNull(firstSegment.Value.Parameters);

            var secondSegment = firstSegment.Value.Parameters;
            Assert.Empty(secondSegment.Matches);

            var thirdSegment = Assert.Single(secondSegment.Literals);
            Assert.Equal("c", thirdSegment.Key);
            Assert.Empty(thirdSegment.Value.Matches);

            var fourthSegment = thirdSegment.Value.Parameters;
            Assert.NotNull(fourthSegment);
            Assert.Empty(fourthSegment.Matches);

            var fifthSegment = Assert.Single(fourthSegment.Literals);
            Assert.Equal("e", fifthSegment.Key);
            Assert.Single(fifthSegment.Value.Matches);

            var sixthSegment = fifthSegment.Value.CatchAlls;
            Assert.NotNull(sixthSegment);
            Assert.Single(sixthSegment.Matches);
        }

        [Fact]
        public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithOptionalValues()
        {
            // Arrange
            var builder = CreateBuilder();

            builder.MapInbound(
                Mock.Of<IRouter>(),
                TemplateParser.Parse("a/{b?}/c"),
                "Intermediate",
                order: 0);

            // Act
            var tree = builder.Build();

            // Assert
            Assert.NotNull(tree);
            Assert.NotNull(tree.MatchingTrees);
            var matchingTree = Assert.Single(tree.MatchingTrees);

            var firstSegment = Assert.Single(matchingTree.Root.Literals);
            Assert.Equal("a", firstSegment.Key);
            Assert.NotNull(firstSegment.Value.Parameters);

            var secondSegment = firstSegment.Value.Parameters;
            Assert.Empty(secondSegment.Matches);

            var thirdSegment = Assert.Single(secondSegment.Literals);
            Assert.Equal("c", thirdSegment.Key);
            Assert.Single(thirdSegment.Value.Matches);
        }

        [Fact]
        public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithConstrainedDefaultValues()
        {
            // Arrange
            var builder = CreateBuilder();

            builder.MapInbound(
                Mock.Of<IRouter>(),
                TemplateParser.Parse("a/{b:int=3}/c"),
                "Intermediate",
                order: 0);

            // Act
            var tree = builder.Build();

            // Assert
            Assert.NotNull(tree);
            Assert.NotNull(tree.MatchingTrees);
            var matchingTree = Assert.Single(tree.MatchingTrees);

            var firstSegment = Assert.Single(matchingTree.Root.Literals);
            Assert.Equal("a", firstSegment.Key);
            Assert.NotNull(firstSegment.Value.ConstrainedParameters);

            var secondSegment = firstSegment.Value.ConstrainedParameters;
            Assert.Empty(secondSegment.Matches);

            var thirdSegment = Assert.Single(secondSegment.Literals);
            Assert.Equal("c", thirdSegment.Key);
            Assert.Single(thirdSegment.Value.Matches);
        }

        [Fact]
        public void TreeRouter_BuildDoesNotAddIntermediateMatchingNodes_ForRoutesWithIntermediateParametersWithConstrainedOptionalValues()
        {
            // Arrange
            var builder = CreateBuilder();

            builder.MapInbound(
                Mock.Of<IRouter>(),
                TemplateParser.Parse("a/{b:int?}/c"),
                "Intermediate",
                order: 0);

            // Act
            var tree = builder.Build();

            // Assert
            Assert.NotNull(tree);
            Assert.NotNull(tree.MatchingTrees);
            var matchingTree = Assert.Single(tree.MatchingTrees);

            var firstSegment = Assert.Single(matchingTree.Root.Literals);
            Assert.Equal("a", firstSegment.Key);
            Assert.NotNull(firstSegment.Value.ConstrainedParameters);

            var secondSegment = firstSegment.Value.ConstrainedParameters;
            Assert.Empty(secondSegment.Matches);

            var thirdSegment = Assert.Single(secondSegment.Literals);
            Assert.Equal("c", thirdSegment.Key);
            Assert.Single(thirdSegment.Value.Matches);
        }

        private static TreeRouteBuilder CreateBuilder()
        {
            var objectPoolProvider = new DefaultObjectPoolProvider();
            var objectPolicy = new UriBuilderContextPooledObjectPolicy();
            var objectPool = objectPoolProvider.Create(objectPolicy);

            var constraintResolver = GetInlineConstraintResolver();
            var builder = new TreeRouteBuilder(
                NullLoggerFactory.Instance,
                objectPool,
                constraintResolver);
            return builder;
        }

        private static IInlineConstraintResolver GetInlineConstraintResolver()
        {
            var services = new ServiceCollection().AddOptions();
            var serviceProvider = services.BuildServiceProvider();
            var accessor = serviceProvider.GetRequiredService<IOptions<RouteOptions>>();
            return new DefaultInlineConstraintResolver(accessor, serviceProvider);
        }
    }
}
