// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;
using Resources = Microsoft.AspNetCore.Mvc.ViewFeatures.Resources;

namespace Microsoft.AspNetCore.Mvc
{
    /// <summary>
    /// A <see cref="RemoteAttributeBase"/> for controllers which configures Unobtrusive validation to send an Ajax request to the
    /// web site. The invoked action should return JSON indicating whether the value is valid.
    /// </summary>
    /// <remarks>Does no server-side validation of the final form submission.</remarks>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public class RemoteAttribute : RemoteAttributeBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="RemoteAttribute"/> class.
        /// </summary>
        /// <remarks>
        /// Intended for subclasses that support URL generation with no route, action, or controller names.
        /// </remarks>
        protected RemoteAttribute()
        { }

        /// <summary>
        /// Initializes a new instance of the <see cref="RemoteAttribute"/> class.
        /// </summary>
        /// <param name="routeName">
        /// The route name used when generating the URL where client should send a validation request.
        /// </param>
        /// <remarks>
        /// Finds the <paramref name="routeName"/> in any area of the application.
        /// </remarks>
        public RemoteAttribute(string routeName)
            : this()
        {
            RouteName = routeName;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RemoteAttribute"/> class.
        /// </summary>
        /// <param name="action">
        /// The action name used when generating the URL where client should send a validation request.
        /// </param>
        /// <param name="controller">
        /// The controller name used when generating the URL where client should send a validation request.
        /// </param>
        /// <remarks>
        /// <para>
        /// If either <paramref name="action"/> or <paramref name="controller"/> is <c>null</c>, uses the corresponding
        /// ambient value.
        /// </para>
        /// <para>Finds the <paramref name="controller"/> in the current area.</para>
        /// </remarks>
        public RemoteAttribute(string action, string controller)
            : this()
        {
            if (action != null)
            {
                RouteData["action"] = action;
            }

            if (controller != null)
            {
                RouteData["controller"] = controller;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RemoteAttribute"/> class.
        /// </summary>
        /// <param name="action">
        /// The action name used when generating the URL where client should send a validation request.
        /// </param>
        /// <param name="controller">
        /// The controller name used when generating the URL where client should send a validation request.
        /// </param>
        /// <param name="areaName">The name of the area containing the <paramref name="controller"/>.</param>
        /// <remarks>
        /// <para>
        /// If either <paramref name="action"/> or <paramref name="controller"/> is <c>null</c>, uses the corresponding
        /// ambient value.
        /// </para>
        /// If <paramref name="areaName"/> is <c>null</c>, finds the <paramref name="controller"/> in the root area.
        /// Use the <see cref="RemoteAttribute(string, string)"/> overload find the <paramref name="controller"/> in
        /// the current area. Or explicitly pass the current area's name as the <paramref name="areaName"/> argument to
        /// this overload.
        /// </remarks>
        public RemoteAttribute(string action, string controller, string areaName)
            : this(action, controller)
        {
            RouteData["area"] = areaName;
        }

        /// <summary>
        /// Gets or sets the route name used when generating the URL where client should send a validation request.
        /// </summary>
        protected string? RouteName { get; set; }

        /// <inheritdoc />
        protected override string GetUrl(ClientModelValidationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var services = context.ActionContext.HttpContext.RequestServices;
            var factory = services.GetRequiredService<IUrlHelperFactory>();
            var urlHelper = factory.GetUrlHelper(context.ActionContext);

            var url = urlHelper.RouteUrl(new UrlRouteContext()
            {
                RouteName = RouteName,
                Values = RouteData,
            });

            if (url == null)
            {
                throw new InvalidOperationException(Resources.RemoteAttribute_NoUrlFound);
            }

            return url;
        }
    }
}
