// 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.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.Internal
{
    public class ValidatorCacheTest
    {
        [Fact]
        public void GetValidators_CachesAllValidators()
        {
            // Arrange
            var cache = new ValidatorCache();
            var metadata = new TestModelMetadataProvider().GetMetadataForProperty(typeof(TypeWithProperty), "Property1");
            var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider();

            // Act - 1
            var validators1 = cache.GetValidators(metadata, validatorProvider);

            // Assert - 1
            var attribute1 = Assert.IsType<DataAnnotationsModelValidator>(validators1[0]).Attribute;
            var attribute2 = Assert.IsType<DataAnnotationsModelValidator>(validators1[1]).Attribute;
            Assert.Contains(attribute1, metadata.ValidatorMetadata); // Copied by provider
            Assert.Contains(attribute2, metadata.ValidatorMetadata); // Copied by provider

            // Act - 2
            var validators2 = cache.GetValidators(metadata, validatorProvider);

            // Assert - 2
            Assert.Same(validators1, validators2);

            Assert.Contains(validators1[0], validators2); // Cached
            Assert.Contains(validators1[1], validators2); // Cached
        }

        [Fact]
        public void GetValidators_DoesNotCacheValidatorsWithIsReusableFalse()
        {
            // Arrange
            var cache = new ValidatorCache();
            var metadata = new TestModelMetadataProvider().GetMetadataForProperty(typeof(TypeWithProperty), "Property1");
            var validatorProvider = new ProviderWithNonReusableValidators();

            // Act - 1
            var validators1 = cache.GetValidators(metadata, validatorProvider);

            // Assert - 1
            var validator1 = Assert.IsType<DataAnnotationsModelValidator>(validators1[0]);
            var validator2 = Assert.IsType<DataAnnotationsModelValidator>(validators1[1]);
            Assert.Contains(validator1.Attribute, metadata.ValidatorMetadata); // Copied by provider
            Assert.Contains(validator2.Attribute, metadata.ValidatorMetadata); // Copied by provider

            // Act - 2
            var validators2 = cache.GetValidators(metadata, validatorProvider);

            // Assert - 2
            Assert.NotSame(validators1, validators2);

            var requiredValidator = Assert.Single(validators2.Where(v => (v as DataAnnotationsModelValidator).Attribute is RequiredAttribute));
            Assert.Contains(requiredValidator, validators1); // cached
            var stringLengthValidator = Assert.Single(validators2.Where(v => (v as DataAnnotationsModelValidator).Attribute is StringLengthAttribute));
            Assert.DoesNotContain(stringLengthValidator, validators1); // not cached
        }

        private class TypeWithProperty
        {
            [Required]
            [StringLength(10)]
            public string Property1 { get; set; }
        }

        private class ProviderWithNonReusableValidators : IModelValidatorProvider
        {
            public void CreateValidators(ModelValidatorProviderContext context)
            {
                for (var i = 0; i < context.Results.Count; i++)
                {
                    var validatorItem = context.Results[i];
                    if (validatorItem.Validator != null)
                    {
                        continue;
                    }

                    var attribute = validatorItem.ValidatorMetadata as ValidationAttribute;
                    if (attribute == null)
                    {
                        continue;
                    }

                    var validator = new DataAnnotationsModelValidator(
                        new ValidationAttributeAdapterProvider(),
                        attribute,
                        stringLocalizer: null);

                    validatorItem.Validator = validator;

                    if (attribute is RequiredAttribute)
                    {
                        validatorItem.IsReusable = true;
                    }
                }
            }
        }
    }
}
