local typedefs = require "kong.db.schema.typedefs"
local redis_schema = require "kong.tools.redis.schema"

local ORDERED_PERIODS = { "second", "minute", "hour", "day", "month", "year" }


local function validate_periods_order(limit)
  for i, lower_period in ipairs(ORDERED_PERIODS) do
    local v1 = limit[lower_period]
    if type(v1) == "number" then
      for j = i + 1, #ORDERED_PERIODS do
        local upper_period = ORDERED_PERIODS[j]
        local v2 = limit[upper_period]
        if type(v2) == "number" and v2 < v1 then
          return nil, string.format("the limit for %s(%.1f) cannot be lower than the limit for %s(%.1f)",
            upper_period, v2, lower_period, v1)
        end
      end
    end
  end

  return true
end


local function is_dbless()
  local _, database, role = pcall(function()
    return kong.configuration.database,
        kong.configuration.role
  end)

  return database == "off" or role == "control_plane"
end


local policy
if is_dbless() then
  policy = {
    description =
    "The rate-limiting policies to use for retrieving and incrementing the limits.",
    type = "string",
    default = "local",
    one_of = {
      "local",
      "redis",
    },
  }
else
  policy = {
    description =
    "The rate-limiting policies to use for retrieving and incrementing the limits.",
    type = "string",
    default = "local",
    one_of = {
      "local",
      "cluster",
      "redis",
    },
  }
end

return {
  name = "response-ratelimiting",
  fields = {
    { protocols = typedefs.protocols_http },
    {
      config = {
        type = "record",
        fields = {
          {
            header_name = {
              description = "The name of the response header used to increment the counters.",
              type = "string",
              default = "x-kong-limit"
            },
          },
          {
            limit_by = {
              description =
              "The entity that will be used when aggregating the limits: `consumer`, `credential`, `ip`. If the `consumer` or the `credential` cannot be determined, the system will always fallback to `ip`.",
              type = "string",
              default = "consumer",
              one_of = { "consumer", "credential", "ip" },
            },
          },
          { policy = policy },
          {
            fault_tolerant = {
              description =
              "A boolean value that determines if the requests should be proxied even if Kong has troubles connecting a third-party datastore. If `true`, requests will be proxied anyway, effectively disabling the rate-limiting function until the datastore is working again. If `false`, then the clients will see `500` errors.",
              type = "boolean",
              required = true,
              default = true
            },
          },
          { redis = redis_schema.config_schema },
          {
            block_on_first_violation = {
              description =
              "A boolean value that determines if the requests should be blocked as soon as one limit is being exceeded. This will block requests that are supposed to consume other limits too.",
              type = "boolean",
              required = true,
              default = false
            },
          },
          {
            hide_client_headers = {
              description = "Optionally hide informative response headers.",
              type = "boolean",
              required = true,
              default = false
            },
          },
          {
            limits = {
              description = "A map that defines rate limits for the plugin.",
              type = "map",
              required = true,
              len_min = 1,
              keys = { type = "string" },
              values = {
                type = "record",
                required = true,
                fields = {
                  { second = { type = "number", gt = 0 }, },
                  { minute = { type = "number", gt = 0 }, },
                  { hour = { type = "number", gt = 0 }, },
                  { day = { type = "number", gt = 0 }, },
                  { month = { type = "number", gt = 0 }, },
                  { year = { type = "number", gt = 0 }, },
                },
                custom_validator = validate_periods_order,
                entity_checks = {
                  { at_least_one_of = ORDERED_PERIODS },
                },
              },
            },
          },
        },
        shorthand_fields = {
          -- TODO: deprecated forms, to be removed in Kong 4.0
          { redis_host = {
            type = "string",
            deprecation = {
              replaced_with = { { path = { 'redis', 'host' } } },
              message = "response-ratelimiting: config.redis_host is deprecated, please use config.redis.host instead",
              removal_in_version = "4.0", },
            func = function(value)
              return { redis = { host = value } }
            end
          } },
          { redis_port = {
            type = "integer",
            deprecation = {
              replaced_with = { { path = {'redis', 'port'} } },
              message = "response-ratelimiting: config.redis_port is deprecated, please use config.redis.port instead",
              removal_in_version = "4.0", },
            func = function(value)
              return { redis = { port = value } }
            end
          } },
          { redis_password = {
            type = "string",
            len_min = 0,
            deprecation = {
              replaced_with = { { path = {'redis', 'password'} } },
              message = "response-ratelimiting: config.redis_password is deprecated, please use config.redis.password instead",
              removal_in_version = "4.0", },
            func = function(value)
              return { redis = { password = value } }
            end
          } },
          { redis_username = {
            type = "string",
            deprecation = {
              replaced_with = { { path = {'redis', 'username'} } },
              message = "response-ratelimiting: config.redis_username is deprecated, please use config.redis.username instead",
              removal_in_version = "4.0", },
            func = function(value)
              return { redis = { username = value } }
            end
          } },
          { redis_ssl = {
            type = "boolean",
            deprecation = {
              replaced_with = { { path = {'redis', 'ssl'} } },
              message = "response-ratelimiting: config.redis_ssl is deprecated, please use config.redis.ssl instead",
              removal_in_version = "4.0", },
            func = function(value)
              return { redis = { ssl = value } }
            end
          } },
          { redis_ssl_verify = {
            type = "boolean",
            deprecation = {
              replaced_with = { { path = {'redis', 'ssl_verify'} } },
              message = "response-ratelimiting: config.redis_ssl_verify is deprecated, please use config.redis.ssl_verify instead",
              removal_in_version = "4.0", },
            func = function(value)
              return { redis = { ssl_verify = value } }
            end
          } },
          { redis_server_name = {
            type = "string",
            deprecation = {
              replaced_with = { { path = {'redis', 'server_name'} } },
              message = "response-ratelimiting: config.redis_server_name is deprecated, please use config.redis.server_name instead",
              removal_in_version = "4.0", },
            func = function(value)
              return { redis = { server_name = value } }
            end
          } },
          { redis_timeout = {
            type = "integer",
            deprecation = {
              replaced_with = { { path = {'redis', 'timeout'} } },
              message = "response-ratelimiting: config.redis_timeout is deprecated, please use config.redis.timeout instead",
              removal_in_version = "4.0", },
            func = function(value)
              return { redis = { timeout = value } }
            end
          } },
          { redis_database = {
            type = "integer",
            deprecation = {
              replaced_with = { { path = {'redis', 'database'} } },
              message = "response-ratelimiting: config.redis_database is deprecated, please use config.redis.database instead",
              removal_in_version = "4.0", },
            func = function(value)
              return { redis = { database = value } }
            end
          } },
        },
      },
    },
  },
  entity_checks = {
    { conditional = {
      if_field = "config.policy", if_match = { eq = "redis" },
      then_field = "config.redis.host", then_match = { required = true },
    } },
    { conditional = {
      if_field = "config.policy", if_match = { eq = "redis" },
      then_field = "config.redis.port", then_match = { required = true },
    } },
    { conditional = {
      if_field = "config.policy", if_match = { eq = "redis" },
      then_field = "config.redis.timeout", then_match = { required = true },
    } },
  },
}
