namespace Kit.Helpers.Routes
{
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Routing;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;

    public class ControllerBaseRouteFluent<TController> : IEndPointFluent<ControllerBaseRouteFluent<TController>>
        where TController :  ControllerBase
    {
        private readonly IRouteBuilder _routeTable;
        private readonly List<RouteFluent> _routes;

        public ControllerBaseRouteFluent(IRouteBuilder routeTable)
        {
            _routeTable = routeTable;
            _routes = new List<RouteFluent>();
        }

        #region Generic Result Actions

        private void AddRouteLambda(string url, LambdaExpression actionSelector)
        {
            // получаем имя контроллера
            string controllerName = typeof(TController).Name;
            if (controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && controllerName.Length > "Controller".Length)
            {
                controllerName = controllerName.Remove(controllerName.Length - "Controller".Length, "Controller".Length);
            }

            // получаем имя действия
            var unaryExpression = (UnaryExpression)actionSelector.Body;
            var methodCallExpression = (MethodCallExpression)unaryExpression.Operand;
            var constantExpression = (ConstantExpression)methodCallExpression.Object;
            var methodInfo = (MethodInfo)constantExpression.Value;
            string actionName = methodInfo.Name;

            _routes.Add( _routeTable.AddRoute($"{controllerName}_{actionName}_{Guid.NewGuid()}",url, controllerName, actionName, methodInfo));
        }

        public delegate TResult GenericResult0Delegate<TResult>();
        public delegate TResult GenericResult1Delegate<TResult, TInput1>(TInput1 input1);
        public delegate TResult GenericResult2Delegate<TResult, TInput1, TInput2>(TInput1 input1, TInput2 input2);
        public delegate TResult GenericResult3Delegate<TResult, TInput1, TInput2, TInput3>(TInput1 input1, TInput2 input2, TInput3 input3);
        public delegate TResult GenericResult4Delegate<TResult, TInput1, TInput2, TInput3, TInput4>(TInput1 input1, TInput2 input2, TInput3 input3, TInput4 input4);
        public delegate TResult GenericResult5Delegate<TResult, TInput1, TInput2, TInput3, TInput4, TInput5>(TInput1 input1, TInput2 input2, TInput3 input3, TInput4 input4, TInput5 input5);

        public ControllerBaseRouteFluent<TController> GenericAction<TResult>(string url, Expression<Func<TController, GenericResult0Delegate<TResult>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> GenericAction<TResult, TInput1>(string url, Expression<Func<TController, GenericResult1Delegate<TResult, TInput1>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> GenericAction<TResult, TInput1, TInput2>(string url, Expression<Func<TController, GenericResult2Delegate<TResult, TInput1, TInput2>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> GenericAction<TResult, TInput1, TInput2, TInput3>(string url, Expression<Func<TController, GenericResult3Delegate<TResult, TInput1, TInput2, TInput3>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> GenericAction<TResult, TInput1, TInput2, TInput3, TInput4>(string url, Expression<Func<TController, GenericResult4Delegate<TResult, TInput1, TInput2, TInput3, TInput4>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }
        public ControllerBaseRouteFluent<TController> GenericAction<TResult, TInput1, TInput2, TInput3, TInput4, TInput5>(string url, Expression<Func<TController, GenericResult5Delegate<TResult, TInput1, TInput2, TInput3, TInput4, TInput5>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        #endregion

        #region JsonResult Actions

        public ControllerBaseRouteFluent<TController> JsonAction(string url, Expression<Func<TController, GenericResult0Delegate<JsonResult>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> JsonAction<TInput1>(string url, Expression<Func<TController, GenericResult1Delegate<JsonResult, TInput1>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> JsonAction<TInput1, TInput2>(string url, Expression<Func<TController, GenericResult2Delegate<JsonResult, TInput1, TInput2>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> JsonAction<TInput1, TInput2, TInput3>(string url, Expression<Func<TController, GenericResult3Delegate<JsonResult, TInput1, TInput2, TInput3>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> JsonAction<TInput1, TInput2, TInput3, TInput4>(string url, Expression<Func<TController, GenericResult4Delegate<JsonResult, TInput1, TInput2, TInput3, TInput4>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> JsonAction<TInput1, TInput2, TInput3, TInput4, TInput5>(string url, Expression<Func<TController, GenericResult5Delegate<JsonResult, TInput1, TInput2, TInput3, TInput4, TInput5>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        #endregion

        #region EmptyResult Actions

        public ControllerBaseRouteFluent<TController> EmptyAction(string url, Expression<Func<TController, GenericResult0Delegate<EmptyResult>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> EmptyAction<TInput1>(string url, Expression<Func<TController, GenericResult1Delegate<EmptyResult, TInput1>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> EmptyAction<TInput1, TInput2>(string url, Expression<Func<TController, GenericResult2Delegate<EmptyResult, TInput1, TInput2>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> EmptyAction<TInput1, TInput2, TInput3>(string url, Expression<Func<TController, GenericResult3Delegate<EmptyResult, TInput1, TInput2, TInput3>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> EmptyAction<TInput1, TInput2, TInput3, TInput4>(string url, Expression<Func<TController, GenericResult4Delegate<EmptyResult, TInput1, TInput2, TInput3, TInput4>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> EmptyAction<TInput1, TInput2, TInput3, TInput4, TInput5>(string url, Expression<Func<TController, GenericResult5Delegate<EmptyResult, TInput1, TInput2, TInput3, TInput4, TInput5>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        #endregion

        #region ContentResult Actions

        public ControllerBaseRouteFluent<TController> ContentAction(string url, Expression<Func<TController, GenericResult0Delegate<ContentResult>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> ContentAction<TInput1>(string url, Expression<Func<TController, GenericResult1Delegate<ContentResult, TInput1>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> ContentAction<TInput1, TInput2>(string url, Expression<Func<TController, GenericResult2Delegate<ContentResult, TInput1, TInput2>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> ContentAction<TInput1, TInput2, TInput3>(string url, Expression<Func<TController, GenericResult3Delegate<ContentResult, TInput1, TInput2, TInput3>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> ContentAction<TInput1, TInput2, TInput3, TInput4>(string url, Expression<Func<TController, GenericResult4Delegate<ContentResult, TInput1, TInput2, TInput3, TInput4>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> ContentAction<TInput1, TInput2, TInput3, TInput4, TInput5>(string url, Expression<Func<TController, GenericResult5Delegate<ContentResult, TInput1, TInput2, TInput3, TInput4, TInput5>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        #endregion

        #region PartialViewResult Actions

        public ControllerBaseRouteFluent<TController> PartialViewAction(string url, Expression<Func<TController, GenericResult0Delegate<PartialViewResult>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> PartialViewAction<TInput1>(string url, Expression<Func<TController, GenericResult1Delegate<PartialViewResult, TInput1>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> PartialViewAction<TInput1, TInput2>(string url, Expression<Func<TController, GenericResult2Delegate<PartialViewResult, TInput1, TInput2>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> PartialViewAction<TInput1, TInput2, TInput3>(string url, Expression<Func<TController, GenericResult3Delegate<PartialViewResult, TInput1, TInput2, TInput3>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> PartialViewAction<TInput1, TInput2, TInput3, TInput4>(string url, Expression<Func<TController, GenericResult4Delegate<PartialViewResult, TInput1, TInput2, TInput3, TInput4>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> PartialViewAction<TInput1, TInput2, TInput3, TInput4, TInput5>(string url, Expression<Func<TController, GenericResult5Delegate<PartialViewResult, TInput1, TInput2, TInput3, TInput4, TInput5>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        #endregion

        #region ActionResult Actions

        public ControllerBaseRouteFluent<TController> ActionAction(string url, Expression<Func<TController, GenericResult0Delegate<IActionResult>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> ActionAction<TInput1>(string url, Expression<Func<TController, GenericResult1Delegate<IActionResult, TInput1>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> ActionAction<TInput1, TInput2>(string url, Expression<Func<TController, GenericResult2Delegate<IActionResult, TInput1, TInput2>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> ActionAction<TInput1, TInput2, TInput3>(string url, Expression<Func<TController, GenericResult3Delegate<IActionResult, TInput1, TInput2, TInput3>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> ActionAction<TInput1, TInput2, TInput3, TInput4>(string url, Expression<Func<TController, GenericResult4Delegate<IActionResult, TInput1, TInput2, TInput3, TInput4>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        public ControllerBaseRouteFluent<TController> ActionAction<TInput1, TInput2, TInput3, TInput4, TInput5>(string url, Expression<Func<TController, GenericResult5Delegate<IActionResult, TInput1, TInput2, TInput3, TInput4, TInput5>>> actionSelector)
        {
            this.AddRouteLambda(url, actionSelector);
            return this;
        }

        #endregion

        #region IRouteFluent<ControllerRouteFluent<TController>>

        public ControllerBaseRouteFluent<TController> NeedHttpGet(bool required = true)
        {
            if (_routes.Any()) _routes.Last().NeedHttpGet(required);
            return this;
        }

        public ControllerBaseRouteFluent<TController> NeedHttpPost(bool required = true)
        {
            if (_routes.Any()) _routes.Last().NeedHttpPost(required);
            return this;
        }

        public ControllerBaseRouteFluent<TController> WithDefaults(object defaults)
        {
            if (_routes.Any()) _routes.Last().WithDefaults(defaults);
            return this;
        }

        public ControllerBaseRouteFluent<TController> WithConstraints(object constraints)
        {
            if (_routes.Any()) _routes.Last().WithConstraints(constraints);
            return this;
        }

        public ControllerBaseRouteFluent<TController> WithNamespaces(string[] namespaces)
        {
            if (_routes.Any()) _routes.Last().WithNamespaces(namespaces);
            return this;
        }

        public ControllerBaseRouteFluent<TController> NeedSecurityKey(string securityKeyName)
        {
            if (_routes.Any()) _routes.Last().NeedSecurityKey(securityKeyName);
            return this;
        }

        public ControllerBaseRouteFluent<TController> WithDataTokens(object dataTokens)
        {
            if (_routes.Any()) _routes.Last().WithDataTokens(dataTokens);
            return this;
        }

        public ControllerBaseRouteFluent<TController> WithDelegate(Action<RouteFluent> routeFluentAction)
        {
            if (_routes.Any()) routeFluentAction(_routes.Last());
            return this;
        }

        #endregion

        #region IRouteFluent<ControllerRouteFluent<TController>> for ALL

        public ControllerBaseRouteFluent<TController> NeedHttpGet_All(bool required = true)
        {
            _routes.ForEach(x => x.NeedHttpGet(required));
            return this;
        }

        public ControllerBaseRouteFluent<TController> NeedHttpPost_All(bool required = true)
        {
            _routes.ForEach(x => x.NeedHttpPost(required));
            return this;
        }

        public ControllerBaseRouteFluent<TController> WithDefaults_All(object defaults)
        {
            _routes.ForEach(x => x.WithDefaults(defaults));
            return this;
        }

        public ControllerBaseRouteFluent<TController> WithConstraints_All(object constraints)
        {
            _routes.ForEach(x => x.WithConstraints(constraints));
            return this;
        }

        public ControllerBaseRouteFluent<TController> WithNamespaces_All(string[] namespaces)
        {
            _routes.ForEach(x => x.WithNamespaces(namespaces));
            return this;
        }

        public ControllerBaseRouteFluent<TController> NeedSecurityKey_All(string securityKeyName)
        {
            _routes.ForEach(x => x.NeedSecurityKey(securityKeyName));
            return this;
        }

        public ControllerBaseRouteFluent<TController> WithDataTokens_All(object dataTokens)
        {
            _routes.ForEach(x => x.WithDataTokens(dataTokens));
            return this;
        }

        public ControllerBaseRouteFluent<TController> WithDelegate_All(Action<RouteFluent> routeFluentAction)
        {
            _routes.ForEach(x => routeFluentAction(x));
            return this;
        }

        #endregion
    }
}