namespace Kit.Helpers.Routes
{
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Routing;
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using System.Reflection;

    public class ControllerMethod
    {
        public string Name { get; }
        public string Controller { get; }
        public string RouteTemplate { get; }
        public string Method => _route.GetMethod();
        public MethodInfo MethodInfo { get; }
        public RouteValueDictionary RouteDataTokens
        {
            get
            {
                var result = new RouteValueDictionary();
                _route.Route.DataTokens?.ForEach(x => result.Add(x.Key, x.Value));
                return result;
            }
        }
        private RouteFluent _route { get; }
        public ControllerMethod(string name, MethodInfo methodInfo, RouteFluent route)
        {
            MethodInfo = methodInfo;
            _route = route;

            Name = name;
            Controller = _route.Route?.Defaults["controller"]?.ToString() ?? string.Empty;
            RouteTemplate = _route.Route?.RouteTemplate ?? string.Empty;
        }
    }

    public static class RouteHelper_Extensions
    {
        public static List<ControllerMethod> ControllerMethods = new List<ControllerMethod>();

        private static IInlineConstraintResolver CreateInlineConstraintResolver(IServiceProvider serviceProvider)
        {
            var inlineConstraintResolver = serviceProvider
                .GetRequiredService<IInlineConstraintResolver>();

            var parameterPolicyFactory = serviceProvider
                .GetRequiredService<ParameterPolicyFactory>();

            // This inline constraint resolver will return a null constraint for non-IRouteConstraint
            // parameter policies so Route does not error
            return new BackCompatInlineConstraintResolver(inlineConstraintResolver, parameterPolicyFactory);
        }

        internal class NullRouteConstraint : IRouteConstraint
        {
            public static readonly NullRouteConstraint Instance = new NullRouteConstraint();

            private NullRouteConstraint()
            {
            }

            public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
            {
                return true;
            }
        }
        private class BackCompatInlineConstraintResolver : IInlineConstraintResolver
        {
            private readonly IInlineConstraintResolver _inner;
            private readonly ParameterPolicyFactory _parameterPolicyFactory;

            public BackCompatInlineConstraintResolver(IInlineConstraintResolver inner, ParameterPolicyFactory parameterPolicyFactory)
            {
                _inner = inner;
                _parameterPolicyFactory = parameterPolicyFactory;
            }

            public IRouteConstraint ResolveConstraint(string inlineConstraint)
            {
                var routeConstraint = _inner.ResolveConstraint(inlineConstraint);
                if (routeConstraint != null)
                {
                    return routeConstraint;
                }

                var parameterPolicy = _parameterPolicyFactory.Create(null, inlineConstraint);
                if (parameterPolicy != null)
                {
                    // Logic inside Route will skip adding NullRouteConstraint
                    return NullRouteConstraint.Instance;
                }

                return null;
            }
        }




        public static RouteFluent AddRoute(this IRouteBuilder routeBuilder, string name, string url, string controller, string action, MethodInfo methodInfo)
        {
            var inline = CreateInlineConstraintResolver(routeBuilder.ServiceProvider);
            var route = new Route(routeBuilder.DefaultHandler, name, url, new RouteValueDictionary(new { controller = controller, action = action }), null, null, inline);
            routeBuilder.Routes.Add(route);
            var result = new RouteFluent { Route = route };
            ControllerMethods.Add(new ControllerMethod(name, methodInfo, result));
            return result;
        }

        public static ControllerBaseRouteFluent<TController> AddControllerBase<TController>(this IRouteBuilder routeTable)
            where TController : ControllerBase
        {
            return new ControllerBaseRouteFluent<TController>(routeTable);
        }

        public static ControllerRouteFluent<TController> AddController<TController>(this IRouteBuilder routeTable)
            where TController : Controller
        {
            return new ControllerRouteFluent<TController>(routeTable);
        }

        public static RouteBase GetBaseRoute(this ControllerContext context)
        {
            return context.GetBaseRoute();
        }

        public static bool GetForbidForReadonly(this RouteData route)
        {
            return (route.DataTokens["forbidForReadonly"] as bool?) ?? false;
        }
    }
}