// 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.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.ControllerFeatureProviderControllers;
using Moq;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.Controllers
{
    public class ControllerFeatureProviderTest
    {
        [Fact]
        public void UserDefinedClass_DerivedFromController_IsController()
        {
            // Arrange
            var controllerType = typeof(StoreController).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            var discovered = Assert.Single(feature.Controllers);
            Assert.Equal(controllerType, discovered);
        }

        [Fact]
        public void UserDefinedClass_DerivedFromControllerBase_IsController()
        {
            // Arrange
            var controllerType = typeof(ProductsController).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            var discovered = Assert.Single(feature.Controllers);
            Assert.Equal(controllerType, discovered);
        }

        [Fact]
        public void UserDefinedClass_DerivedFromControllerBaseWithoutSuffix_IsController()
        {
            // Arrange
            var controllerType = typeof(Products).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            var discovered = Assert.Single(feature.Controllers);
            Assert.Equal(controllerType, discovered);
        }

        [Fact]
        public void FrameworkControllerClass_IsNotController()
        {
            // Arrange
            var controllerType = typeof(Controller).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            Assert.Empty(feature.Controllers);
        }

        [Fact]
        public void FrameworkBaseControllerClass_IsNotController()
        {
            // Arrange
            var controllerType = typeof(ControllerBase).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            Assert.Empty(feature.Controllers);
        }

        [Fact]
        public void UserDefinedControllerClass_IsNotController()
        {
            // Arrange
            var controllerType = typeof(ControllerFeatureProviderControllers.Controller).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            Assert.Empty(feature.Controllers);
        }

        [Fact]
        public void Interface_IsNotController()
        {
            // Arrange
            var controllerType = typeof(ITestController).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            Assert.Empty(feature.Controllers);
        }
        
        [Fact]
        public void ValueTypeClass_IsNotController()
        {
            // Arrange
            var controllerType = typeof(int).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            Assert.Empty(feature.Controllers);
        }

        [Fact]
        public void AbstractClass_IsNotController()
        {
            // Arrange
            var controllerType = typeof(AbstractController).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            Assert.Empty(feature.Controllers);
        }

        [Fact]
        public void DerivedAbstractClass_IsController()
        {
            // Arrange
            var controllerType = typeof(DerivedAbstractController).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            var discovered = Assert.Single(feature.Controllers);
            Assert.Equal(controllerType, discovered);
        }

        [Fact]
        public void OpenGenericClass_IsNotController()
        {
            // Arrange
            var controllerType = typeof(OpenGenericController<>).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            Assert.Empty(feature.Controllers);
        }

        [Fact]
        public void WithoutSuffixOrAncestorWithController_IsNotController()
        {
            // Arrange
            var controllerType = typeof(NoSuffixPoco).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            Assert.Empty(feature.Controllers);
        }

        [Fact]
        public void ClosedGenericClass_IsController()
        {
            // Arrange
            var controllerType = typeof(OpenGenericController<string>).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            var discovered = Assert.Single(feature.Controllers);
            Assert.Equal(controllerType, discovered);
        }

        [Fact]
        public void DerivedGenericClass_IsController()
        {
            // Arrange
            var controllerType = typeof(DerivedGenericController).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            var discovered = Assert.Single(feature.Controllers);
            Assert.Equal(controllerType, discovered);
        }

        [Fact]
        public void Poco_WithNamingConvention_IsController()
        {
            // Arrange
            var controllerType = typeof(PocoController).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            var discovered = Assert.Single(feature.Controllers);
            Assert.Equal(controllerType, discovered);
        }

        [Fact]
        public void NoControllerSuffix_IsController()
        {
            // Arrange
            var controllerType = typeof(NoSuffix).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            var discovered = Assert.Single(feature.Controllers);
            Assert.Equal(controllerType, discovered);
        }

        [Theory]
        [InlineData(typeof(DescendantLevel1))]
        [InlineData(typeof(DescendantLevel2))]
        public void AncestorTypeHasControllerAttribute_IsController(Type type)
        {
            // Arrange
            var manager = GetApplicationPartManager(type.GetTypeInfo());
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            var discovered = Assert.Single(feature.Controllers);
            Assert.Equal(type.GetTypeInfo(), discovered);
        }

        [Fact]
        public void AncestorTypeDoesNotHaveControllerAttribute_IsNotController()
        {
            // Arrange
            var controllerType = typeof(NoSuffixNoControllerAttribute).GetTypeInfo();
            var manager = GetApplicationPartManager(controllerType);
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            Assert.Empty(feature.Controllers);
        }

        [Fact]
        public void GetFeature_OnlyRunsOnParts_ThatImplementIApplicationPartTypeProvider()
        {
            // Arrange
            var otherPart = new Mock<ApplicationPart>();
            otherPart
                .As<IApplicationPartTypeProvider>()
                .Setup(t => t.Types)
                .Returns(new[] { typeof(PocoController).GetTypeInfo() });

            var parts = new[] {
                Mock.Of<ApplicationPart>(),
                new TestApplicationPart(typeof(NoSuffix).GetTypeInfo()),
                otherPart.Object
            };

            var feature = new ControllerFeature();

            var expected = new List<TypeInfo>
            {
                typeof(NoSuffix).GetTypeInfo(),
                typeof(PocoController).GetTypeInfo()
            };

            var provider = new ControllerFeatureProvider();

            // Act
            provider.PopulateFeature(parts, feature);

            // Assert
            Assert.Equal(expected, feature.Controllers.ToList());
        }

        [Fact]
        public void GetFeature_DoesNotAddDuplicates_ToTheListOfControllers()
        {
            // Arrange
            var otherPart = new Mock<ApplicationPart>();
            otherPart
                .As<IApplicationPartTypeProvider>()
                .Setup(t => t.Types)
                .Returns(new[] { typeof(PocoController).GetTypeInfo() });

            var parts = new[] {
                Mock.Of<ApplicationPart>(),
                new TestApplicationPart(typeof(NoSuffix)),
                otherPart.Object
            };

            var feature = new ControllerFeature();

            var expected = new List<TypeInfo>
            {
                typeof(NoSuffix).GetTypeInfo(),
                typeof(PocoController).GetTypeInfo()
            };

            var provider = new ControllerFeatureProvider();

            provider.PopulateFeature(parts, feature);

            // Act
            provider.PopulateFeature(parts, feature);

            // Assert
            Assert.Equal(expected, feature.Controllers.ToList());
        }

        [Theory]
        [InlineData(typeof(BaseNonControllerController))]
        [InlineData(typeof(BaseNonControllerControllerChild))]
        [InlineData(typeof(BasePocoNonControllerController))]
        [InlineData(typeof(BasePocoNonControllerControllerChild))]
        [InlineData(typeof(NonController))]
        [InlineData(typeof(NonControllerChild))]
        [InlineData(typeof(BaseNonControllerAttributeChildControllerControllerAttributeController))]
        [InlineData(typeof(PersonModel))] // Verifies that POCO type hierarchies that don't derive from controller return false.
        public void IsController_ReturnsFalse_IfTypeOrAncestorHasNonControllerAttribute(Type type)
        {
            // Arrange
            var manager = GetApplicationPartManager(type.GetTypeInfo());
            var feature = new ControllerFeature();

            // Act
            manager.PopulateFeature(feature);

            // Assert
            Assert.Empty(feature.Controllers);
        }

        private static ApplicationPartManager GetApplicationPartManager(params TypeInfo[] types)
        {
            var manager = new ApplicationPartManager();
            manager.ApplicationParts.Add(new TestApplicationPart(types));
            manager.FeatureProviders.Add(new ControllerFeatureProvider());

            return manager;
        }
    }
}

// These controllers are used to test the ControllerFeatureProvider implementation
// which REQUIRES that they be public top-level classes. To avoid having to stub out the
// implementation of this class to test it, they are just top level classes. Don't reuse
// these outside this test - find a better way or use nested classes to keep the tests
// independent.
namespace Microsoft.AspNetCore.Mvc.ControllerFeatureProviderControllers
{
    public abstract class AbstractController : Controller
    {
    }

    public class DerivedAbstractController : AbstractController
    {
    }

    public class StoreController : Controller
    {
    }

    public class ProductsController : ControllerBase
    {
    }

    public class Products : ControllerBase
    {
    }

    [Controller]
    public abstract class Controller
    {
    }

    public abstract class NoControllerAttributeBaseController
    {
    }

    public class NoSuffixNoControllerAttribute : NoControllerAttributeBaseController
    {
    }

    public class OpenGenericController<T> : Controller
    {
    }

    public class DerivedGenericController : OpenGenericController<string>
    {
    }

    public interface ITestController
    {
    }

    public class NoSuffix : Controller
    {
    }

    public class NoSuffixPoco
    {

    }

    public class PocoController
    {
    }

    [Controller]
    public class CustomBase
    {

    }

    [Controller]
    public abstract class CustomAbstractBaseController
    {

    }

    public class DescendantLevel1 : CustomBase
    {

    }

    public class DescendantLevel2 : DescendantLevel1
    {

    }

    public class AbstractChildWithoutSuffix : CustomAbstractBaseController
    {

    }

    [NonController]
    public class BasePocoNonControllerController
    {

    }

    [Controller]
    public class BaseNonControllerAttributeChildControllerControllerAttributeController : BaseNonControllerController
    {
 
    }

    public class BasePocoNonControllerControllerChild : BasePocoNonControllerController
    {

    }

    [NonController]
    public class BaseNonControllerController : Controller
    {

    }

    public class BaseNonControllerControllerChild : BaseNonControllerController
    {

    }

    [NonController]
    public class NonControllerChild : Controller
    {

    }

    [NonController]
    public class NonController : Controller
    {

    }

    public class DataModelBase
    {

    }

    public class EntityDataModel : DataModelBase
    {

    }

    public class PersonModel : EntityDataModel
    {

    }
}