// 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.Globalization;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Analyzer.Testing;
using Microsoft.CodeAnalysis;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
{
    public class ApiConventionAnalyzerIntegrationTest
    {
        private MvcDiagnosticAnalyzerRunner Executor { get; } = new ApiConventionWith1006DiagnosticEnabledRunner();

        [Fact]
        public Task NoDiagnosticsAreReturned_ForNonApiController()
            => RunNoDiagnosticsAreReturned();

        [Fact]
        public Task NoDiagnosticsAreReturned_ForRazorPageModels()
            => RunNoDiagnosticsAreReturned();

        [Fact]
        public Task NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes()
            => RunNoDiagnosticsAreReturned();

        [Fact]
        public Task NoDiagnosticsAreReturned_ForOkResultReturningAction()
            => RunNoDiagnosticsAreReturned();

        [Fact]
        public Task NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred()
            => RunNoDiagnosticsAreReturned();

        [Fact]
        public Task NoDiagnosticsAreReturned_ForReturnStatementsInLambdas()
            => RunNoDiagnosticsAreReturned();

        [Fact]
        public Task NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions()
            => RunNoDiagnosticsAreReturned();

        [Fact]
        public async Task DiagnosticsAreReturned_ForIncompleteActionResults()
        {
            // Arrange
            var source = @"
using Microsoft.AspNetCore.Mvc;

namespace Test
{
    [ApiController]
    [Route(""[controller]/[action]"")
    public class TestController : ControllerBase
    {
        public IActionResult Get(int id)
        {
            if (id == 0)
            {
                return /*MM*/NotFound();
            }

            return;
        }
    }
}";
            var testSource = TestSource.Read(source);
            var expectedLocation = testSource.DefaultMarkerLocation;

            // Act
            var result = await Executor.GetDiagnosticsAsync(testSource.Source);

            // Assert
            var diagnostic = Assert.Single(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
            AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location);
        }

        [Fact]
        public async Task NoDiagnosticsAreReturned_WhenActionDoesNotCompile()
        {
            // Arrange
            var source = @"
namespace Test
{
    [ApiController]
    [Route(""[controller]/[action]"")
    public class TestController : ControllerBase
    {
        public IActionResult Get(int id)
        {
            if (id == 0)
            {
                return NotFound();
            }

            return Ok();
        }
    }
}";

            // Act
            var result = await Executor.GetDiagnosticsAsync(source);

            // Assert
            Assert.DoesNotContain(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
        }

        [Fact]
        public async Task DiagnosticsAreReturned_ForNotFoundNullActionResults()
        {
            // Arrange
            var source = @"
using Microsoft.AspNetCore.Mvc;

namespace Test
{
    [ApiController]
    [Route(""[controller]"")]
    public class TestController : ControllerBase
    {
        [HttpGet]
        public ActionResult<string> Test()
        {
            return NotFound(null);
        }
    }
}";
            var testSource = TestSource.Read(source);
            var expectedLocation = testSource.DefaultMarkerLocation;

            // Act
            var result = await Executor.GetDiagnosticsAsync(testSource.Source);

            // Assert
            Assert.Contains(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id);
        }

        [Fact]
        public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode()
            => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 404);

        [Fact]
        public Task DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode()
            => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 404);

        [Fact]
        public Task DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode()
            => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 200);

        [Fact]
        public Task DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes()
            => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 404);

        [Fact]
        public Task DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes()
            => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 422);

        [Fact]
        public Task DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode()
            => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 400);

        [Fact]
        public Task DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode()
            => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 202);

        [Fact]
        public Task DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation()
            => RunTest(ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult);

        [Fact]
        public Task DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation()
            => RunTest(ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult);

        [Fact]
        public Task DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType()
            => RunTest(ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult);

        [Fact]
        public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode()
            => RunTest(ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode, 400);

        [Fact]
        public Task DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode()
            => RunTest(ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode, 404);

        [Fact]
        public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode()
            => RunTest(ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode, 200);

        private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "")
        {
            // Arrange
            var testSource = MvcTestSource.Read(GetType().Name, testMethod);
            var expectedLocation = testSource.DefaultMarkerLocation;

            // Act
            var result = await Executor.GetDiagnosticsAsync(testSource.Source);

            // Assert
            Assert.Empty(result);
        }

        private Task RunTest(DiagnosticDescriptor descriptor, [CallerMemberName] string testMethod = "")
            => RunTest(descriptor, Array.Empty<object>(), testMethod);

        private Task RunTest(DiagnosticDescriptor descriptor, int statusCode, [CallerMemberName] string testMethod = "")
            => RunTest(descriptor, new[] { statusCode.ToString(CultureInfo.InvariantCulture) }, testMethod);

        private async Task RunTest(DiagnosticDescriptor descriptor, object[] args, [CallerMemberName] string testMethod = "")
        {
            // Arrange
            var testSource = MvcTestSource.Read(GetType().Name, testMethod);
            var expectedLocation = testSource.DefaultMarkerLocation;

            // Act
            var result = await Executor.GetDiagnosticsAsync(testSource.Source);

            // Assert
            Assert.Collection(
                result,
                diagnostic =>
                {
                    Assert.Equal(descriptor.Id, diagnostic.Id);
                    Assert.Same(descriptor, diagnostic.Descriptor);
                    AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location);
                    Assert.Equal(string.Format(CultureInfo.InvariantCulture, descriptor.MessageFormat.ToString(CultureInfo.InvariantCulture), args), diagnostic.GetMessage(CultureInfo.InvariantCulture));
                });
        }
        private class ApiConventionWith1006DiagnosticEnabledRunner : MvcDiagnosticAnalyzerRunner
        {
            public ApiConventionWith1006DiagnosticEnabledRunner() : base(new ApiConventionAnalyzer())
            {
            }

            protected override CompilationOptions ConfigureCompilationOptions(CompilationOptions options)
            {
                var compilationOptions = base.ConfigureCompilationOptions(options);

                // 10006 is disabled by default. Explicitly enable it so we can correctly validate no diagnostics
                // are returned scenarios.
                var specificDiagnosticOptions = compilationOptions.SpecificDiagnosticOptions.Add(
                    ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode.Id,
                    ReportDiagnostic.Info);

                return compilationOptions.WithSpecificDiagnosticOptions(specificDiagnosticOptions);
            }
        }
    }
}
