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

namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
{
    // Integration tests for the default configuration of ModelMetadata and Validation providers
    public class DefaultModelValidatorProviderTest
    {
        [Fact]
        public void CreateValidators_ForIValidatableObject()
        {
            // Arrange
            var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
            var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider();

            var metadata = metadataProvider.GetMetadataForType(typeof(ValidatableObject));
            var context = new ModelValidatorProviderContext(metadata, GetValidatorItems(metadata));

            // Act
            validatorProvider.CreateValidators(context);

            // Assert
            var validatorItems = context.Results;

            var validatorItem = Assert.Single(validatorItems);
            Assert.IsType<ValidatableObjectAdapter>(validatorItem.Validator);
        }

        [Fact]
        public void CreateValidators_ModelValidatorAttributeOnClass()
        {
            // Arrange
            var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
            var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider();

            var metadata = metadataProvider.GetMetadataForType(typeof(ModelValidatorAttributeOnClass));
            var context = new ModelValidatorProviderContext(metadata, GetValidatorItems(metadata));

            // Act
            validatorProvider.CreateValidators(context);

            // Assert
            var validatorItems = context.Results;

            var validator = Assert.IsType<CustomModelValidatorAttribute>(Assert.Single(validatorItems).Validator);
            Assert.Equal("Class", validator.Tag);
        }

        [Fact]
        public void CreateValidators_ModelValidatorAttributeOnProperty()
        {
            // Arrange
            var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
            var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider();

            var metadata = metadataProvider.GetMetadataForProperty(
                typeof(ModelValidatorAttributeOnProperty),
                nameof(ModelValidatorAttributeOnProperty.Property));
            var context = new ModelValidatorProviderContext(metadata, GetValidatorItems(metadata));

            // Act
            validatorProvider.CreateValidators(context);

            // Assert
            var validatorItems = context.Results;

            var validator = Assert.IsType<CustomModelValidatorAttribute>(Assert.Single(validatorItems).Validator);
            Assert.Equal("Property", validator.Tag);
        }

        [Fact]
        public void CreateValidators_ModelValidatorAttributeOnPropertyAndClass()
        {
            // Arrange
            var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
            var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider();

            var metadata = metadataProvider.GetMetadataForProperty(
                typeof(ModelValidatorAttributeOnPropertyAndClass),
                nameof(ModelValidatorAttributeOnPropertyAndClass.Property));
            var context = new ModelValidatorProviderContext(metadata, GetValidatorItems(metadata));

            // Act
            validatorProvider.CreateValidators(context);

            // Assert
            var validatorItems = context.Results;

            Assert.Equal(2, validatorItems.Count);
            Assert.Single(validatorItems, v => Assert.IsType<CustomModelValidatorAttribute>(v.Validator).Tag == "Class");
            Assert.Single(validatorItems, v => Assert.IsType<CustomModelValidatorAttribute>(v.Validator).Tag == "Property");
        }

        [Fact]
        public void CreateValidators_FromModelMetadataType_SingleValidator()
        {
            // Arrange
            var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
            var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider();

            var metadata = metadataProvider.GetMetadataForProperty(
                typeof(ProductViewModel),
                nameof(ProductViewModel.Id));
            var context = new ModelValidatorProviderContext(metadata, GetValidatorItems(metadata));

            // Act
            validatorProvider.CreateValidators(context);

            // Assert
            var validatorItems = context.Results;

            var adapter = Assert.IsType<DataAnnotationsModelValidator>(Assert.Single(validatorItems).Validator);
            Assert.IsType<RangeAttribute>(adapter.Attribute);
        }

        [Fact]
        public void CreateValidators_FromModelMetadataType_MergedValidators()
        {
            // Arrange
            var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
            var validatorProvider = TestModelValidatorProvider.CreateDefaultProvider();

            var metadata = metadataProvider.GetMetadataForProperty(
                typeof(ProductViewModel),
                nameof(ProductViewModel.Name));
            var context = new ModelValidatorProviderContext(metadata, GetValidatorItems(metadata));

            // Act
            validatorProvider.CreateValidators(context);

            // Assert
            var validatorItems = context.Results;

            Assert.Equal(2, validatorItems.Count);
            Assert.Single(validatorItems, v => ((DataAnnotationsModelValidator)v.Validator).Attribute is RegularExpressionAttribute);
            Assert.Single(validatorItems, v => ((DataAnnotationsModelValidator)v.Validator).Attribute is StringLengthAttribute);
        }

        [Fact]
        public void HasValidators_ReturnsTrue_IfMetadataIsIModelValidator()
        {
            // Arrange
            var validatorProvider = new DefaultModelValidatorProvider();
            var attributes = new object[] { new RequiredAttribute(), new CustomModelValidatorAttribute(), new BindRequiredAttribute(), };

            // Act
            var result = validatorProvider.HasValidators(typeof(object), attributes);

            // Assert
            Assert.True(result);
        }

        [Fact]
        public void HasValidators_ReturnsFalse_IfNoMetadataIsIModelValidator()
        {
            // Arrange
            var validatorProvider = new DefaultModelValidatorProvider();
            var attributes = new object[] { new RequiredAttribute(), new BindRequiredAttribute(), };

            // Act
            var result = validatorProvider.HasValidators(typeof(object), attributes);

            // Assert
            Assert.False(result);
        }

        private static IList<ValidatorItem> GetValidatorItems(ModelMetadata metadata)
        {
            return metadata.ValidatorMetadata.Select(v => new ValidatorItem(v)).ToList();
        }

        private class ValidatableObject : IValidatableObject
        {
            public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
            {
                return null;
            }
        }

        [CustomModelValidator(Tag = "Class")]
        private class ModelValidatorAttributeOnClass
        {
        }

        private class ModelValidatorAttributeOnProperty
        {
            [CustomModelValidator(Tag = "Property")]
            public string Property { get; set; }
        }

        private class ModelValidatorAttributeOnPropertyAndClass
        {
            [CustomModelValidator(Tag = "Property")]
            public ModelValidatorAttributeOnClass Property { get; set; }
        }

        private class CustomModelValidatorAttribute : Attribute, IModelValidator
        {
            public string Tag { get; set; }

            public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
            {
                throw new NotImplementedException();
            }
        }

        private class RangeAttributeOnProperty
        {
            [Range(0, 10)]
            public int Property { get; set; }
        }

        private class CustomValidationAttribute : ValidationAttribute
        {
        }

        private class CustomValidationAttributeOnProperty
        {
            [CustomValidation]
            public int Property { get; set; }
        }

        private class ProductEntity
        {
            [Range(0, 10)]
            public int Id { get; set; }

            [RegularExpression(".*")]
            public string Name { get; set; }
        }

        [ModelMetadataType(typeof(ProductEntity))]
        private class ProductViewModel
        {
            public int Id { get; set; }

            [StringLength(4)]
            public string Name { get; set; }
        }
    }
}