// 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.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;

namespace Microsoft.AspNetCore.Mvc;

public class RedirectToPageResultTest
{
    [Fact]
    public async Task ExecuteResultAsync_ThrowsOnNullUrl()
    {
        // Arrange
        var httpContext = new DefaultHttpContext
        {
            RequestServices = CreateServices(),
        };

        var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

        var urlHelper = GetUrlHelper(actionContext, returnValue: null);
        var result = new RedirectToPageResult("/some-page", new Dictionary<string, object>())
        {
            UrlHelper = urlHelper,
        };

        // Act & Assert
        await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
            () => result.ExecuteResultAsync(actionContext),
            "No page named '/some-page' matches the supplied values.");
    }

    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public async Task ExecuteResultAsync_PassesCorrectValuesToRedirect(bool permanentRedirect)
    {
        // Arrange
        var expectedUrl = "SampleAction";

        var httpContext = new Mock<HttpContext>();
        var httpResponse = new Mock<HttpResponse>();
        httpContext.SetupGet(c => c.RequestServices)
            .Returns(CreateServices());
        httpContext.SetupGet(c => c.Response)
            .Returns(httpResponse.Object);

        var actionContext = new ActionContext(
            httpContext.Object,
            new RouteData(),
            new ActionDescriptor());

        var urlHelper = GetUrlHelper(actionContext, expectedUrl);
        var result = new RedirectToPageResult("/MyPage", null, new { id = 10, test = "value" }, permanentRedirect)
        {
            UrlHelper = urlHelper,
        };

        // Act
        await result.ExecuteResultAsync(actionContext);

        // Assert
        httpResponse.Verify(r => r.Redirect(expectedUrl, permanentRedirect), Times.Exactly(1));
    }

    [Fact]
    public async Task ExecuteResultAsync_LocalRelativePaths()
    {
        // Arrange
        var httpContext = new DefaultHttpContext
        {
            RequestServices = CreateServices(),
        };

        var pageContext = new ActionContext
        {
            HttpContext = httpContext,
            RouteData = new RouteData(),
            ActionDescriptor = new ActionDescriptor(),
        };

        pageContext.RouteData.Values.Add("page", "/A/Redirecting/Page");

        UrlRouteContext context = null;
        var urlHelper = new Mock<IUrlHelper>();
        urlHelper.SetupGet(h => h.ActionContext).Returns(pageContext);
        urlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))
            .Callback((UrlRouteContext c) => context = c)
            .Returns("some-value");
        var values = new { test = "test-value" };
        var result = new RedirectToPageResult("./", "page-handler", values, true, "test-fragment")
        {
            UrlHelper = urlHelper.Object,
            Protocol = "ftp",
        };

        // Act
        await result.ExecuteResultAsync(pageContext);

        // Assert
        Assert.NotNull(context);
        Assert.Null(context.RouteName);
        Assert.Collection(Assert.IsType<RouteValueDictionary>(context.Values),
            value =>
            {
                Assert.Equal("test", value.Key);
                Assert.Equal("test-value", value.Value);
            },
            value =>
            {
                Assert.Equal("page", value.Key);
                Assert.Equal("/A/Redirecting", value.Value);
            },
            value =>
            {
                Assert.Equal("handler", value.Key);
                Assert.Equal("page-handler", value.Value);
            });
        Assert.Equal("ftp", context.Protocol);
        Assert.Equal("test-fragment", context.Fragment);
    }

    [Fact]
    public async Task ExecuteResultAsync_WithAllParameters()
    {
        // Arrange
        var httpContext = new DefaultHttpContext
        {
            RequestServices = CreateServices(),
        };

        var pageContext = new ActionContext
        {
            HttpContext = httpContext,
            RouteData = new RouteData(),
        };

        UrlRouteContext context = null;
        var urlHelper = new Mock<IUrlHelper>();
        urlHelper.SetupGet(h => h.ActionContext).Returns(pageContext);
        urlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))
            .Callback((UrlRouteContext c) => context = c)
            .Returns("some-value");
        var values = new { test = "test-value" };
        var result = new RedirectToPageResult("/MyPage", "page-handler", values, true, "test-fragment")
        {
            UrlHelper = urlHelper.Object,
            Protocol = "ftp",
        };

        // Act
        await result.ExecuteResultAsync(pageContext);

        // Assert
        Assert.NotNull(context);
        Assert.Null(context.RouteName);
        Assert.Collection(Assert.IsType<RouteValueDictionary>(context.Values),
            value =>
            {
                Assert.Equal("test", value.Key);
                Assert.Equal("test-value", value.Value);
            },
            value =>
            {
                Assert.Equal("page", value.Key);
                Assert.Equal("/MyPage", value.Value);
            },
            value =>
            {
                Assert.Equal("handler", value.Key);
                Assert.Equal("page-handler", value.Value);
            });
        Assert.Equal("ftp", context.Protocol);
        Assert.Equal("test-fragment", context.Fragment);
    }

    [Fact]
    public async Task RedirectToPage_WithNullPage_UsesAmbientValue()
    {
        // Arrange
        var expected = "path/to/this-page";
        var httpContext = new Mock<HttpContext>();
        var httpResponse = new Mock<HttpResponse>();
        httpContext.SetupGet(c => c.Response)
            .Returns(httpResponse.Object);
        httpContext.SetupGet(c => c.RequestServices)
            .Returns(CreateServices());
        var routeData = new RouteData
        {
            Values =
                {
                    ["page"] = expected,
                }
        };

        var actionContext = new ActionContext(
            httpContext.Object,
            routeData,
            new ActionDescriptor());

        UrlRouteContext context = null;
        var urlHelper = new Mock<IUrlHelper>();
        urlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))
            .Callback((UrlRouteContext c) => context = c)
            .Returns("some-value");
        urlHelper.SetupGet(h => h.ActionContext)
            .Returns(actionContext);
        var pageName = (string)null;
        var result = new RedirectToPageResult(pageName)
        {
            UrlHelper = urlHelper.Object,
        };

        // Act
        await result.ExecuteResultAsync(actionContext);

        // Assert
        Assert.NotNull(context);
        Assert.Collection(Assert.IsType<RouteValueDictionary>(context.Values),
           value =>
           {
               Assert.Equal("page", value.Key);
               Assert.Equal(expected, value.Value);
           });
    }

    [Fact]
    public async Task RedirectToPage_DoesNotUseAmbientHandler()
    {
        // Arrange
        var expected = "path/to/this-page";
        var httpContext = new Mock<HttpContext>();
        var httpResponse = new Mock<HttpResponse>();
        httpContext.SetupGet(c => c.Response)
            .Returns(httpResponse.Object);
        httpContext.SetupGet(c => c.RequestServices)
            .Returns(CreateServices());
        var routeData = new RouteData
        {
            Values =
                {
                    ["page"] = expected,
                    ["handler"] = "delete",
                }
        };

        var actionContext = new ActionContext(
            httpContext.Object,
            routeData,
            new ActionDescriptor());

        UrlRouteContext context = null;
        var urlHelper = new Mock<IUrlHelper>();
        urlHelper.Setup(h => h.RouteUrl(It.IsAny<UrlRouteContext>()))
            .Callback((UrlRouteContext c) => context = c)
            .Returns("some-value");
        urlHelper.SetupGet(h => h.ActionContext)
            .Returns(actionContext);
        var pageName = (string)null;
        var result = new RedirectToPageResult(pageName)
        {
            UrlHelper = urlHelper.Object,
        };

        // Act
        await result.ExecuteResultAsync(actionContext);

        // Assert
        Assert.NotNull(context);
        Assert.Collection(Assert.IsType<RouteValueDictionary>(context.Values),
           value =>
           {
               Assert.Equal("page", value.Key);
               Assert.Equal(expected, value.Value);
           },
           value =>
           {
               Assert.Equal("handler", value.Key);
               Assert.Null(value.Value);
           });
    }

    private static IServiceProvider CreateServices(IUrlHelperFactory factory = null)
    {
        var services = new ServiceCollection();
        services.AddSingleton<IActionResultExecutor<RedirectToPageResult>, RedirectToPageResultExecutor>();

        if (factory != null)
        {
            services.AddSingleton(factory);
        }
        else
        {
            services.AddSingleton<IUrlHelperFactory, UrlHelperFactory>();
        }

        services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
        return services.BuildServiceProvider();
    }

    private static IUrlHelper GetUrlHelper(ActionContext context, string returnValue)
    {
        var urlHelper = new Mock<IUrlHelper>();
        urlHelper.SetupGet(h => h.ActionContext).Returns(context);
        urlHelper.Setup(o => o.RouteUrl(It.IsAny<UrlRouteContext>())).Returns(returnValue);
        return urlHelper.Object;
    }
}
