// 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.ModelBinding;
using Microsoft.Extensions.Primitives;

namespace Microsoft.AspNetCore.Mvc.IntegrationTests;

// Integration tests for the decision logic about how a model-name/prefix is selected at the top-level
// of ModelBinding.
public class ModelPrefixSelectionIntegrationTest
{
    private class Person1
    {
        [FromForm]
        public string Name { get; set; }
    }

    [Fact]
    public async Task ComplexModel_PrefixSelected_ByValueProvider()
    {
        // Arrange
        var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
        var parameter = new ParameterDescriptor()
        {
            Name = "parameter",
            ParameterType = typeof(Person1),
        };

        var testContext = ModelBindingTestHelper.GetTestContext(request =>
        {
            // This will cause selection of the "parameter" prefix.
            request.QueryString = new QueryString("?parameter=");

            // This value won't be used, because we select the "parameter" prefix.
            request.Form = new FormCollection(new Dictionary<string, StringValues>()
            {
                    { "Name", "Billy" },
            });
        });

        var modelState = testContext.ModelState;

        // Act
        var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);

        // Assert
        Assert.True(modelBindingResult.IsModelSet);

        var model = Assert.IsType<Person1>(modelBindingResult.Model);
        Assert.Null(model.Name);

        Assert.Empty(modelState);
        Assert.Equal(0, modelState.ErrorCount);
        Assert.True(modelState.IsValid);
    }

    private class Person2
    {
        [FromForm]
        public string Name { get; set; }
    }

    [Fact]
    public async Task ComplexModel_PrefixSelected_ByValueProviderValue_WithFilteredValueProviders()
    {
        // Arrange
        var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
        var parameter = new ParameterDescriptor()
        {
            Name = "parameter",
            ParameterType = typeof(Person2),
            BindingInfo = new BindingInfo()
            {
                BindingSource = BindingSource.Query,
            },
        };

        var testContext = ModelBindingTestHelper.GetTestContext(request =>
        {
            // This will cause selection of the "parameter" prefix.
            request.QueryString = new QueryString("?parameter=");

            // This value won't be used, because we select the "parameter" prefix.
            request.Form = new FormCollection(new Dictionary<string, StringValues>()
            {
                    { "Name", "Billy" },
            });
        });

        var modelState = testContext.ModelState;

        // Act
        var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);

        // Assert
        Assert.True(modelBindingResult.IsModelSet);

        var model = Assert.IsType<Person2>(modelBindingResult.Model);
        Assert.Null(model.Name);

        Assert.Empty(modelState);
        Assert.Equal(0, modelState.ErrorCount);
        Assert.True(modelState.IsValid);
    }

    private class Person3
    {
        [FromForm]
        public string Name { get; set; }
    }

    [Fact]
    public async Task ComplexModel_EmptyPrefixSelected_NoMatchingValueProviderValue()
    {
        // Arrange
        var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
        var parameter = new ParameterDescriptor()
        {
            Name = "parameter",
            ParameterType = typeof(Person3),
        };

        var testContext = ModelBindingTestHelper.GetTestContext(request =>
        {
            // This can't be used because of [FromForm] on the property.
            request.QueryString = new QueryString("?Name=");
        });

        var modelState = testContext.ModelState;

        // Act
        var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);

        // Assert
        Assert.True(modelBindingResult.IsModelSet);

        var model = Assert.IsType<Person3>(modelBindingResult.Model);
        Assert.Null(model.Name);

        Assert.Empty(modelState);
        Assert.Equal(0, modelState.ErrorCount);
        Assert.True(modelState.IsValid);
    }

    private class Person4
    {
        [FromForm]
        public string Name { get; set; }
    }

    [Fact]
    public async Task ComplexModel_EmptyPrefixSelected_NoMatchingValueProviderValue_WithFilteredValueProviders()
    {
        // Arrange
        var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
        var parameter = new ParameterDescriptor()
        {
            Name = "parameter",
            ParameterType = typeof(Person4),
            BindingInfo = new BindingInfo()
            {
                BindingSource = BindingSource.Query,
            },
        };

        var testContext = ModelBindingTestHelper.GetTestContext(request =>
        {
            // This will only match empty prefix, but can't be used because of [FromForm] on the property.
            request.QueryString = new QueryString("?Name=");

            // This value won't be used to select a prefix, because we're only looking at the query string.
            request.Form = new FormCollection(new Dictionary<string, StringValues>()
            {
                    { "parameter", string.Empty },
            });
        });

        var modelState = testContext.ModelState;

        // Act
        var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);

        // Assert
        Assert.True(modelBindingResult.IsModelSet);

        var model = Assert.IsType<Person4>(modelBindingResult.Model);
        Assert.Null(model.Name);

        Assert.Empty(modelState);
        Assert.Equal(0, modelState.ErrorCount);
        Assert.True(modelState.IsValid);
    }

    private class Person5
    {
        [FromForm]
        public string Name { get; set; }
    }

    [Fact]
    public async Task ComplexModel_EmptyPrefixSelected_NoMatchingValueProviderValue_WithFilteredValueProviders_NoValues()
    {
        // Arrange
        var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
        var parameter = new ParameterDescriptor()
        {
            Name = "parameter",
            ParameterType = typeof(Person5),
            BindingInfo = new BindingInfo()
            {
                BindingSource = BindingSource.Query,
            },
        };

        var testContext = ModelBindingTestHelper.GetTestContext(request =>
        {
            // This value won't be used to select a prefix, because we're only looking at the query string.
            request.Form = new FormCollection(new Dictionary<string, StringValues>()
            {
                    { "parameter", string.Empty },
            });
        });

        var modelState = testContext.ModelState;

        // Act
        var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);

        // Assert
        Assert.True(modelBindingResult.IsModelSet);

        var model = Assert.IsType<Person5>(modelBindingResult.Model);
        Assert.Null(model.Name);

        Assert.Empty(modelState);
        Assert.Equal(0, modelState.ErrorCount);
        Assert.True(modelState.IsValid);
    }
}
