// 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 System;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Testing;
using Xunit;

namespace Microsoft.AspNetCore.Mvc
{
    public class ApiConventionMethodAttributeTest
    {
        [Fact]
        public void Constructor_ThrowsIfConventionMethodIsAnnotatedWithProducesAttribute()
        {
            // Arrange
            var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get);
            var attribute = typeof(ProducesAttribute);

            var expected = GetErrorMessage(methodName, attribute);

            // Act & Assert
            ExceptionAssert.ThrowsArgument(
                () => new ApiConventionMethodAttribute(typeof(ConventionWithProducesAttribute), nameof(ConventionWithProducesAttribute.Get)),
                "conventionType",
                expected);
        }

        public static class ConventionWithProducesAttribute
        {
            [Produces(typeof(void))]
            public static void Get() { }
        }

        [Fact]
        public void Constructor_ThrowsIfTypeIsNotStatic()
        {
            // Arrange
            var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get);
            var attribute = typeof(ProducesAttribute);

            var expected = $"API convention type '{typeof(object)}' must be a static type.";

            // Act & Assert
            ExceptionAssert.ThrowsArgument(
                () => new ApiConventionMethodAttribute(typeof(object), nameof(object.ToString)),
                "conventionType",
                expected);
        }

        [Fact]
        public void Constructor_ThrowsIfMethodCannotBeFound()
        {
            // Arrange
            var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get);
            var attribute = typeof(ProducesAttribute);
            var type = typeof(TestConventions);

            var expected = $"A method named 'DoesNotExist' was not found on convention type '{type}'.";

            // Act & Assert
            ExceptionAssert.ThrowsArgument(
                () => new ApiConventionMethodAttribute(typeof(TestConventions), "DoesNotExist"),
                "methodName",
                expected);
        }

        [Fact]
        public void Constructor_ThrowsIfMethodIsNotPublic()
        {
            // Arrange
            var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get);
            var attribute = typeof(ProducesAttribute);
            var type = typeof(TestConventions);

            var expected = $"A method named 'NotPublic' was not found on convention type '{type}'.";

            // Act & Assert
            ExceptionAssert.ThrowsArgument(
                () => new ApiConventionMethodAttribute(typeof(TestConventions), "NotPublic"),
                "methodName",
                expected);
        }

        [Fact]
        public void Constructor_ThrowsIfMethodIsAmbiguous()
        {
            // Arrange
            var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get);
            var attribute = typeof(ProducesAttribute);
            var type = typeof(TestConventions);

            var expected = $"Method name 'Method' is ambiguous for convention type '{type}'. More than one method found with the name 'Method'.";

            // Act & Assert
            ExceptionAssert.ThrowsArgument(
                () => new ApiConventionMethodAttribute(typeof(TestConventions), nameof(TestConventions.Method)),
                "methodName",
                expected);
        }

        private static class TestConventions
        {
            internal static void NotPublic() { }

            public static void Method(int value) { }

            public static void Method(string value) { }
        }

        private static string GetErrorMessage(string methodName, params Type[] attributes)
        {
            return $"Method {methodName} is decorated with the following attributes that are not allowed on an API convention method:" +
                Environment.NewLine +
                string.Join(Environment.NewLine, attributes.Select(a => a.FullName)) +
                Environment.NewLine +
                $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ProducesDefaultResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}";
        }
    }
}
