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

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Testing;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
    public class DefaultPageHandlerMethodSelectorTest
    {
        [Fact]
        public void NewBehavior_Select_ReturnsFuzzyMatchForHead_WhenNoHeadHandlerDefined()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "GET"
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST"
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                    },
                },
                RouteData = new RouteData(),
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "HEAD"
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Same(descriptor1, actual);
        }

        [Fact]
        public void NewBehavior_Select_PrefersExactMatch()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "GET"
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST"
            };

            var descriptor3 = new HandlerMethodDescriptor
            {
                HttpMethod = "HEAD"
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                        descriptor3,
                    },
                },
                RouteData = new RouteData(),
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "HEAD",
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Same(descriptor3, actual);
        }

        [Fact]
        public void NewBehavior_Select_PrefersExactMatch_ReturnsNullWhenHandlerNameDoesntMatch()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "GET"
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST"
            };

            // This will match the HTTP method 'round' of selection, but won't match the
            // handler name.
            var descriptor3 = new HandlerMethodDescriptor
            {
                HttpMethod = "HEAD",
                Name = "not-provided",
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                        descriptor3,
                    },
                },
                RouteData = new RouteData(),
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "HEAD",
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Null(actual);
        }

        [Theory]
        [InlineData("HEAD")]
        [InlineData("heAd")]
        public void NewBehavior_Select_ReturnsFuzzyMatch_SafeVerbs(string httpMethod)
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "GET"
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST"
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                    },
                },
                RouteData = new RouteData(),
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = httpMethod
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Same(descriptor1, actual);
        }

        [Fact]
        public void Select_ReturnsOnlyHandler()
        {
            // Arrange
            var descriptor = new HandlerMethodDescriptor
            {
                HttpMethod = "GET"
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor,
                    },
                },
                RouteData = new RouteData(),
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "GET"
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Same(descriptor, actual);
        }

        [Theory]
        [InlineData("GET")]
        [InlineData("POST")]
        public void Select_ReturnsHandlerWithMatchingHttpRequestMethod(string httpMethod)
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "PUT",
            };
            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = httpMethod,
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                    },
                },
                RouteData = new RouteData(),
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = httpMethod,
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Same(descriptor2, actual);
        }

        [Fact]
        public void Select_ReturnsNullWhenNoHandlerMatchesHandler()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Add",
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Delete",
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                    },
                },
                RouteData = new RouteData
                {
                    Values =
                    {
                        { "handler", "update" }
                    }
                },
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "POST"
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Null(actual);
        }

        [Fact]
        public void Select_ReturnsHandlerThatMatchesHandler()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Add",
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Delete",
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                    },
                },
                RouteData = new RouteData
                {
                    Values =
                    {
                        { "handler", "Add" }
                    }
                },
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "Post"
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Same(descriptor1, actual);
        }

        [Fact]
        [ReplaceCulture("de-CH", "de-CH")]
        public void Select_ReturnsHandlerThatMatchesHandler_UsesInvariantCulture()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "10/31/2018 07:37:38 -07:00",
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Delete",
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                    },
                },
                RouteData = new RouteData
                {
                    Values =
                    {
                        { "handler", new DateTimeOffset(2018, 10, 31, 7, 37, 38, TimeSpan.FromHours(-7)) },
                    }
                },
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "Post"
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Same(descriptor1, actual);
        }

        [Fact]
        public void Select_HandlerFromQueryString()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Add",
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Delete",
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                    },
                },
                RouteData = new RouteData(),
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "Post",
                        QueryString = new QueryString("?handler=Delete"),
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Same(descriptor2, actual);
        }

        [Fact]
        public void Select_HandlerConsidersRouteDataFirst()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Add",
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Delete",
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                    },
                },
                RouteData = new RouteData
                {
                    Values =
                    {
                        { "handler", "Add" }
                    }
                },
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "Post",
                        QueryString = new QueryString("?handler=Delete"),
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Same(descriptor1, actual);
        }

        [Fact]
        public void Select_HandlerMultipleTimesInQueryString_UsesFirst()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Add",
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Delete",
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                    },
                },
                RouteData = new RouteData(),
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "Post",
                        QueryString = new QueryString("?handler=Delete&handler=Add"),
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Same(descriptor2, actual);
        }

        [Fact]
        public void Select_ReturnsHandlerWithMatchingHttpMethodWithoutAHandler()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
                Name = "Subscribe",
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                HttpMethod = "POST",
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                    },
                },
                RouteData = new RouteData
                {
                    Values =
                    {
                        { "handler", "Add" }
                    }
                },
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "Post"
                    },
                },
            };
            var selector = CreateSelector();

            // Act
            var actual = selector.Select(pageContext);

            // Assert
            Assert.Same(descriptor2, actual);
        }

        [Fact]
        public void Select_WithoutHandler_ThrowsIfMoreThanOneHandlerMatches()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                MethodInfo = GetType().GetMethod(nameof(Post)),
                HttpMethod = "POST",
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                MethodInfo = GetType().GetMethod(nameof(PostAsync)),
                HttpMethod = "POST",
            };

            var descriptor3 = new HandlerMethodDescriptor
            {
                HttpMethod = "GET",
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                        descriptor3,
                    },
                },
                RouteData = new RouteData(),
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "Post"
                    },
                },
            };
            var selector = CreateSelector();

            // Act & Assert
            var ex = Assert.Throws<InvalidOperationException>(() => selector.Select(pageContext));
            var methods = descriptor1.MethodInfo + ", " + descriptor2.MethodInfo;
            var message = "Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:" +
                Environment.NewLine + Environment.NewLine + methods;

            Assert.Equal(message, ex.Message);
        }

        [Fact]
        public void Select_WithHandler_ThrowsIfMoreThanOneHandlerMatches()
        {
            // Arrange
            var descriptor1 = new HandlerMethodDescriptor
            {
                MethodInfo = GetType().GetMethod(nameof(Post)),
                HttpMethod = "POST",
                Name = "Add",
            };

            var descriptor2 = new HandlerMethodDescriptor
            {
                MethodInfo = GetType().GetMethod(nameof(PostAsync)),
                HttpMethod = "POST",
                Name = "Add",
            };

            var descriptor3 = new HandlerMethodDescriptor
            {
                HttpMethod = "GET",
            };

            var pageContext = new PageContext
            {
                ActionDescriptor = new CompiledPageActionDescriptor
                {
                    HandlerMethods = new List<HandlerMethodDescriptor>()
                    {
                        descriptor1,
                        descriptor2,
                        descriptor3,
                    },
                },
                RouteData = new RouteData
                {
                    Values =
                    {
                        { "handler", "Add" }
                    }
                },
                HttpContext = new DefaultHttpContext
                {
                    Request =
                    {
                        Method = "Post"
                    },
                },
            };
            var selector = CreateSelector();

            // Act & Assert
            var ex = Assert.Throws<InvalidOperationException>(() => selector.Select(pageContext));
            var methods = descriptor1.MethodInfo + ", " + descriptor2.MethodInfo;
            var message = "Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:" +
                Environment.NewLine + Environment.NewLine + methods;

            Assert.Equal(message, ex.Message);
        }

        protected void Post()
        {
        }

        protected void PostAsync()
        {
        }

        private static DefaultPageHandlerMethodSelector CreateSelector()
        {
            return new DefaultPageHandlerMethodSelector();
        }
    }
}
