namespace Kit.Helpers.Routes
{
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Routing;
    using Microsoft.AspNetCore.Routing.Constraints;

    public class BodyConstraint : IRouteConstraint, IParameterPolicy
    {
        private readonly bool _required;
        public BodyConstraint(bool required)
        {
            _required = required;
        }
        public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
        {
            bool hasBody = string.IsNullOrWhiteSpace(httpContext?.Request.ContentType) == false;
            return _required == hasBody;
        }
    }

    public class RouteFluent : IEndPointFluent<RouteFluent>
    {
        public Route Route { get; set; }

        public RouteFluent NeedHttpGet(bool required = true)
        {
            this.Route.Constraints.SetByKey("httpMethod", new HttpMethodRouteConstraint("GET"));
            return this;
        }

        public string GetMethod()
        {
            if (IsMethodPost()) return "POST";
            if (IsMethodPut()) return "PUT";
            if (IsMethodDelete()) return "DELETE";
            return "GET";
        }

        public bool IsMethodNotSpecified()
        {
            return this.Route.Constraints.ContainsKey("httpMethod") == false;
        }

        public bool IsMethodGet()
        {
            if (this.Route.Constraints.ContainsKey("httpMethod") == false) return false;
            return (this.Route.Constraints.GetByKey("httpMethod") as HttpMethodRouteConstraint)!.AllowedMethods.Contains("GET");
        }

        public RouteFluent NeedHttpPost(bool required = true)
        {
            this.Route.Constraints.SetByKey("httpMethod", new HttpMethodRouteConstraint("POST"));
            return this;
        }

        public bool IsMethodPost()
        {
            if (this.Route.Constraints.ContainsKey("httpMethod") == false) return false;
            return (this.Route.Constraints.GetByKey("httpMethod") as HttpMethodRouteConstraint)!.AllowedMethods.Contains("POST");
        }

        public RouteFluent NeedHttpPut(bool required = true)
        {
            this.Route.Constraints.SetByKey("httpMethod", new HttpMethodRouteConstraint("PUT"));
            return this;
        }

        public bool IsMethodPut()
        {
            if (this.Route.Constraints.ContainsKey("httpMethod") == false) return false;
            return (this.Route.Constraints.GetByKey("httpMethod") as HttpMethodRouteConstraint)!.AllowedMethods.Contains("PUT");
        }

        public RouteFluent NeedHttpDelete(bool required = true)
        {
            this.Route.Constraints.SetByKey("httpMethod", new HttpMethodRouteConstraint("DELETE"));
            return this;
        }

        public bool IsMethodDelete()
        {
            if (this.Route.Constraints.ContainsKey("httpMethod") == false) return false;
            return (this.Route.Constraints.GetByKey("httpMethod") as HttpMethodRouteConstraint)!.AllowedMethods.Contains("DELETE");
        }

        public RouteFluent NeedSecurityKey(string securityKeyName)
        {
            this.Route.DataTokens["APP_SecurityKeyName"] = securityKeyName;
            return this;
        }

        public RouteFluent WithBody(bool required = true)
        {
            this.Route.Constraints.SetByKey("withBody", new BodyConstraint(required));
            return this;
        }

        public RouteFluent ForbidForReadonly(bool value = true)
        {
            this.Route.DataTokens["forbidForReadonly"] = value;
            return this;
        }

        public RouteFluent WithDefaults(object defaults)
        {
            if (defaults == null) return this;
            foreach (var pair in new RouteValueDictionary(defaults))
            {
                this.Route.Defaults.Add(pair.Key, pair.Value);
            }
            return this;
        }

        public RouteFluent WithConstraints(object constraints)
        {
            if (constraints == null) return this;
            foreach (var pair in new RouteValueDictionary(constraints))
            {
                this.Route.Constraints.SetByKey(pair.Key, (IRouteConstraint)pair.Value);
            }
            return this;
        }

        public RouteFluent WithNamespaces(string[] namespaces)
        {
            if (namespaces == null) return this;
            this.Route.DataTokens["Namespaces"] = namespaces;
            return this;
        }

        public RouteFluent WithDataTokens(object dataTokens)
        {
            if (dataTokens == null) return this;
            foreach (var pair in new RouteValueDictionary(dataTokens))
            {
                this.Route.DataTokens[pair.Key] = pair.Value;
            }
            return this;
        }
    }
}