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

using System.Globalization;
using Microsoft.AspNetCore.Http.RequestDelegateGenerator;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Http.Generators.Tests;

public partial class CompileTimeCreationTests : RequestDelegateCreationTests
{
    [Theory]
    [InlineData("ParameterListRecordStruct")]
    [InlineData("ParameterListRecordClass")]
    [InlineData("ParameterListStruct")]
    [InlineData("ParameterListClass")]
    public async Task RequestDelegateThrowsWhenNullableParameterList(string parameterType)
    {
        var source = $$"""
void TestAction(HttpContext context, [AsParameters] {{parameterType}}? args)
{
    context.Items.Add("value", args);
}
app.MapGet("/", TestAction);
""";

        var (generatorRunResult, compilation) = await RunGeneratorAsync(source);

        // Emits diagnostic but generates no source
        var result = Assert.IsType<GeneratorRunResult>(generatorRunResult);
        var diagnostic = Assert.Single(result.Diagnostics);
        Assert.Equal(DiagnosticDescriptors.InvalidAsParametersNullable.Id, diagnostic.Id);
        Assert.Empty(result.GeneratedSources);
    }

    public static object[][] BadArgumentListActions
    {
        get
        {
            static string GetAbstractTypeError(Type type)
                => $"The abstract type '{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}' is not supported. For more information, please see https://aka.ms/aspnet/rdg-known-issues";

            static string GetMultipleContructorsError(Type type)
                => $"Only a single public parameterized constructor is allowed for type '{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}'. For more information, please see https://aka.ms/aspnet/rdg-known-issues";

            static string GetNoContructorsError(Type type)
                => $"No public parameterless constructor found for type '{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}'. For more information, please see https://aka.ms/aspnet/rdg-known-issues";

            static string GetInvalidConstructorError(Type type)
                => $"The public parameterized constructor must contain only parameters that match the declared public properties for type '{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}'. For more information, please see https://aka.ms/aspnet/rdg-known-issues";

            static string GetEnumerableTypeError(Type type)
                => $"The enumerable type '{TypeNameHelper.GetTypeDisplayName(type, fullName: false)}' is not supported. For more information, please see https://aka.ms/aspnet/rdg-known-issues";

            return new []
            {
                    new object[] { "BadArgumentListRecord", DiagnosticDescriptors.InvalidAsParametersSingleConstructorOnly.Id, GetMultipleContructorsError(typeof(BadArgumentListRecord)) },
                    new object[] { "BadArgumentListClass", DiagnosticDescriptors.InvalidAsParametersSignature.Id, GetInvalidConstructorError(typeof(BadArgumentListClass)) },
                    new object[] { "BadArgumentListClassMultipleCtors", DiagnosticDescriptors.InvalidAsParametersSingleConstructorOnly.Id, GetMultipleContructorsError(typeof(BadArgumentListClassMultipleCtors))  },
                    new object[] { "BadAbstractArgumentListClass", DiagnosticDescriptors.InvalidAsParametersAbstractType.Id, GetAbstractTypeError(typeof(BadAbstractArgumentListClass)) },
                    new object[] { "BadNoPublicConstructorArgumentListClass", DiagnosticDescriptors.InvalidAsParametersNoConstructorFound.Id, GetNoContructorsError(typeof(BadNoPublicConstructorArgumentListClass)) },
                    new object[] { "List<string>", DiagnosticDescriptors.InvalidAsParametersEnumerableType.Id, GetEnumerableTypeError(typeof(List<string>)) },
                    new object[] { "List<IFormFile>", DiagnosticDescriptors.InvalidAsParametersEnumerableType.Id, GetEnumerableTypeError(typeof(List<IFormFile>)) },
                    new object[] { "Dictionary<string, string>", DiagnosticDescriptors.InvalidAsParametersEnumerableType.Id, GetEnumerableTypeError(typeof(Dictionary<string, string>)) }
            };
        }
    }

    [Theory]
    [MemberData(nameof(BadArgumentListActions))]
    public async Task BuildRequestDelegateEmitsDiagnosticForInvalidParameterListConstructor(
        string parameterType,
        string diagnosticId,
         string message)
    {
        var source = $$"""
void TestAction(HttpContext context, [AsParameters] {{parameterType}} args)
{
    context.Items.Add("value", args);
}
app.MapGet("/", TestAction);
""";

        var (generatorRunResult, _) = await RunGeneratorAsync(source);

        // Emits diagnostic but generates no source
        var result = Assert.IsType<GeneratorRunResult>(generatorRunResult);
        var diagnostic = Assert.Single(result.Diagnostics);
        Assert.Equal(diagnosticId, diagnostic.Id);
        Assert.Equal(message, diagnostic.GetMessage(CultureInfo.InvariantCulture));
        Assert.Empty(result.GeneratedSources);
    }

    [Theory]
    [InlineData("NestedArgumentListRecord")]
    [InlineData("ClassWithParametersConstructor")]
    public async Task BuildRequestDelegateThrowsNotSupportedExceptionForNestedParametersList(string parameterType)
    {
        var source = $$"""
void TestAction([AsParameters] {{parameterType}} req) { }
app.MapGet("/", TestAction);
""";
        var (generatorRunResult, _) = await RunGeneratorAsync(source);

        // Emits diagnostic but generates no source
        var result = Assert.IsType<GeneratorRunResult>(generatorRunResult);
        var diagnostic = Assert.Single(result.Diagnostics);
        Assert.Equal(DiagnosticDescriptors.InvalidAsParametersNested.Id, diagnostic.Id);
        Assert.Empty(result.GeneratedSources);
    }
}
