// 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.Reflection;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Routing;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.ApplicationModels
{
    public class ControllerModelTest
    {
        [Fact]
        public void CopyConstructor_DoesDeepCopyOfOtherModels()
        {
            // Arrange
            var controller = new ControllerModel(typeof(TestController).GetTypeInfo(),
                                                 new List<object>());

            var action = new ActionModel(typeof(TestController).GetMethod("Edit"),
                                         new List<object>());
            controller.Actions.Add(action);
            action.Controller = controller;

            controller.ControllerProperties.Add(new PropertyModel(
                controller.ControllerType.AsType().GetProperty("TestProperty"),
                new List<object>() { }));

            var route = new AttributeRouteModel(new HttpGetAttribute("api/Products"));
            controller.Selectors.Add(new SelectorModel() { AttributeRouteModel = route });

            var apiExplorer = controller.ApiExplorer;
            controller.ApiExplorer.GroupName = "group";
            controller.ApiExplorer.IsVisible = true;

            // Act
            var controller2 = new ControllerModel(controller);

            // Assert
            Assert.NotSame(action, controller2.Actions[0]);
            Assert.NotNull(controller2.ControllerProperties);
            Assert.Single(controller2.ControllerProperties);
            Assert.NotNull(controller2.Selectors);
            Assert.Single(controller2.Selectors);
            Assert.NotSame(route, controller2.Selectors[0].AttributeRouteModel);
            Assert.NotSame(apiExplorer, controller2.ApiExplorer);

            Assert.NotSame(controller.Selectors[0].ActionConstraints, controller2.Selectors[0].ActionConstraints);
            Assert.NotSame(controller.Actions, controller2.Actions);
            Assert.NotSame(controller.Attributes, controller2.Attributes);
            Assert.NotSame(controller.Filters, controller2.Filters);
            Assert.NotSame(controller.RouteValues, controller2.RouteValues);

            Assert.NotSame(controller, controller2.Actions[0].Controller);
            Assert.Same(controller2, controller2.Actions[0].Controller);
            Assert.NotSame(controller, controller2.ControllerProperties[0].Controller);
            Assert.Same(controller2, controller2.ControllerProperties[0].Controller);
        }

        [Fact]
        public void CopyConstructor_CopiesAllProperties()
        {
            // Arrange
            var controller = new ControllerModel(
                typeof(TestController).GetTypeInfo(),
                new List<object>()
                {
                    new HttpGetAttribute(),
                    new MyFilterAttribute(),
                });

            var selectorModel = new SelectorModel();
            selectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new string[] { "GET" }));
            controller.Selectors.Add(selectorModel);
            controller.Application = new ApplicationModel();
            controller.ControllerName = "cool";
            controller.Filters.Add(new MyFilterAttribute());
            controller.RouteValues.Add("key", "value");
            controller.Properties.Add(new KeyValuePair<object, object>("test key", "test value"));
            controller.ControllerProperties.Add(
                new PropertyModel(typeof(TestController).GetProperty("TestProperty"), new List<object>()));

            // Act
            var controller2 = new ControllerModel(controller);

            // Assert
            foreach (var property in typeof(ControllerModel).GetProperties())
            {
                if (property.Name.Equals("Actions") ||
                    property.Name.Equals("Selectors") ||
                    property.Name.Equals("ApiExplorer") ||
                    property.Name.Equals("ControllerProperties"))
                {
                    // This test excludes other ApplicationModel objects on purpose because we deep copy them.
                    continue;
                }

                var value1 = property.GetValue(controller);
                var value2 = property.GetValue(controller2);

                if (typeof(IEnumerable<object>).IsAssignableFrom(property.PropertyType))
                {
                    Assert.Equal<object>((IEnumerable<object>)value1, (IEnumerable<object>)value2);

                    // Ensure non-default value
                    Assert.NotEmpty((IEnumerable<object>)value1);
                }
                else if (typeof(IDictionary<string, string>).IsAssignableFrom(property.PropertyType))
                {
                    Assert.Equal(value1, value2);

                    // Ensure non-default value
                    Assert.NotEmpty((IDictionary<string, string>)value1);
                }
                else if (typeof(IDictionary<object, object>).IsAssignableFrom(property.PropertyType))
                {
                    Assert.Equal(value1, value2);

                    // Ensure non-default value
                    Assert.NotEmpty((IDictionary<object, object>)value1);
                }
                else if (property.PropertyType.GetTypeInfo().IsValueType ||
                    Nullable.GetUnderlyingType(property.PropertyType) != null)
                {
                    Assert.Equal(value1, value2);

                    // Ensure non-default value
                    Assert.NotEqual(value1, Activator.CreateInstance(property.PropertyType));
                }
                else if (property.Name.Equals(nameof(ControllerModel.DisplayName)))
                {
                    // DisplayName is re-calculated, hence reference equality wouldn't work.
                    Assert.Equal(value1, value2);
                }
                else
                {
                    Assert.Same(value1, value2);

                    // Ensure non-default value
                    Assert.NotNull(value1);
                }
            }
        }

        private class TestController
        {
            public string TestProperty { get; set; }

            public void Edit()
            {
            }
        }

        private class MyFilterAttribute : Attribute, IFilterMetadata
        {
        }

        private class MyRouteValueAttribute : Attribute, IRouteValueProvider
        {
            public string RouteKey { get; set; }

            public string RouteValue { get; set; }
        }
    }
}