local cjson = require "cjson"
local api_helpers = require "kong.api.api_helpers"
local Schema = require "kong.db.schema"
local Errors = require "kong.db.errors"
local process = require "ngx.process"
local wasm = require "kong.runloop.wasm"

local kong = kong
local meta = require "kong.meta"
local knode  = (kong and kong.node) and kong.node or
               require "kong.pdk.node".new()
local errors = Errors.new()
local get_sys_filter_level = require "ngx.errlog".get_sys_filter_level
local LOG_LEVELS = require "kong.constants".LOG_LEVELS


local tagline = "Welcome to " .. _KONG._NAME
local version = meta.version
local lua_version = jit and jit.version or _VERSION


local strip_foreign_schemas = function(fields)
  for _, field in ipairs(fields) do
    local fname = next(field)
    local fdata = field[fname]
    if fdata["type"] == "foreign" then
      fdata.schema = nil
    end
  end
end


local function validate_schema(db_entity_name, params)
  local entity = kong.db[db_entity_name]
  local schema = entity and entity.schema or nil
  if not schema then
    return kong.response.exit(404, { message = "No entity named '"
                              .. db_entity_name .. "'" })
  end
  local schema = assert(Schema.new(schema))
  local _, err_t = schema:validate(schema:process_auto_fields(params, "insert"))
  if err_t then
    return kong.response.exit(400, errors:schema_violation(err_t))
  end
  return kong.response.exit(200, { message = "schema validation successful" })
end

local default_filter_config_schema
do
  local default

  function default_filter_config_schema(db)
    if default then
      return default
    end

    local dao = db.filter_chains or kong.db.filter_chains
    for key, field in dao.schema:each_field() do
      if key == "filters" then
        for _, ffield in ipairs(field.elements.fields) do
          if ffield.config and ffield.config.json_schema then
            default = ffield.config.json_schema.default
            return default
          end
        end
      end
    end
  end
end


return {
  ["/"] = {
    GET = function(self, dao, helpers)
      local distinct_plugins = setmetatable({}, cjson.array_mt)
      local pids = {
        master = process.get_master_pid()
      }

      do
        local set = {}
        for row, err in kong.db.plugins:each() do
          if err then
            kong.log.err(err)
            return kong.response.exit(500, { message = "An unexpected error happened" })
          end

          if not set[row.name] then
            distinct_plugins[#distinct_plugins+1] = row.name
            set[row.name] = true
          end
        end
      end

      do
        local kong_shm = ngx.shared.kong
        local worker_count = ngx.worker.count() - 1
        for i = 0, worker_count do
          local worker_pid, err = kong_shm:get("pids:" .. i)
          if not worker_pid then
            err = err or "not found"
            ngx.log(ngx.ERR, "could not get worker process id for worker #", i , ": ", err)

          else
            if not pids.workers then
              pids.workers = {}
            end

            pids.workers[i + 1] = worker_pid
          end
        end
      end

      local node_id, err = knode.get_id()
      if node_id == nil then
        ngx.log(ngx.ERR, "could not get node id: ", err)
      end

      local available_plugins = {}
      for name in pairs(kong.configuration.loaded_plugins) do
        available_plugins[name] = {
          version = kong.db.plugins.handlers[name].VERSION,
          priority = kong.db.plugins.handlers[name].PRIORITY,
        }
      end

      local configuration = kong.configuration.remove_sensitive()
      configuration.log_level = LOG_LEVELS[get_sys_filter_level()]

      return kong.response.exit(200, {
        tagline = tagline,
        version = version,
        edition = meta._VERSION:match("enterprise") and "enterprise" or "community",
        hostname = knode.get_hostname(),
        node_id = node_id,
        timers = {
          running = ngx.timer.running_count(),
          pending = ngx.timer.pending_count(),
        },
        plugins = {
          available_on_server = available_plugins,
          enabled_in_cluster = distinct_plugins,
        },
        lua_version = lua_version,
        configuration = configuration,
        pids = pids,
      })
    end
  },
  ["/endpoints"] = {
    GET = function(self, dao, helpers)
      local endpoints = setmetatable({}, cjson.array_mt)
      local application = require("kong.api")
      local each_route = require("lapis.application.route_group").each_route
      local filled_endpoints = {}
      each_route(application, true, function(path)
        if type(path) == "table" then
          path = next(path)
        end
        if not filled_endpoints[path] then
          filled_endpoints[path] = true
          endpoints[#endpoints + 1] = path:gsub(":([^/:]+)", function(m)
            return "{" .. m .. "}"
          end)
        end
      end)
      table.sort(endpoints, function(a, b)
        -- when sorting use lower-ascii char for "/" to enable segment based
        -- sorting, so not this:
        --   /a
        --   /ab
        --   /ab/a
        --   /a/z
        -- But this:
        --   /a
        --   /a/z
        --   /ab
        --   /ab/a
        return a:gsub("/", "\x00") < b:gsub("/", "\x00")
      end)

      return kong.response.exit(200, { data = endpoints })
    end
  },
  ["/schemas/:name"] = {
    GET = function(self, db, helpers)
      local entity = kong.db[self.params.name]
      local schema = entity and entity.schema or nil
      if not schema then
        return kong.response.exit(404, { message = "No entity named '"
                                      .. self.params.name .. "'" })
      end
      local copy = api_helpers.schema_to_jsonable(schema)
      strip_foreign_schemas(copy.fields)
      return kong.response.exit(200, copy)
    end
  },
  ["/schemas/plugins/validate"] = {
    POST = function(self, db, helpers)
      return validate_schema("plugins", self.params)
    end
  },
  ["/schemas/vaults/validate"] = {
    POST = function(self, db, helpers)
      return validate_schema("vaults", self.params)
    end
  },
  ["/schemas/:db_entity_name/validate"] = {
    POST = function(self, db, helpers)
      local db_entity_name = self.params.db_entity_name
      -- What happens when db_entity_name is a field name in the schema?
      self.params.db_entity_name = nil
      return validate_schema(db_entity_name, self.params)
    end
  },

  ["/schemas/vaults/:name"] = {
    GET = function(self, db, helpers)
      local subschema = kong.db.vaults.schema.subschemas[self.params.name]
      if not subschema then
        return kong.response.exit(404, { message = "No vault named '"
                                  .. self.params.name .. "'" })
      end
      local copy = api_helpers.schema_to_jsonable(subschema)
      strip_foreign_schemas(copy.fields)
      return kong.response.exit(200, copy)
    end
  },
  ["/schemas/plugins/:name"] = {
    GET = function(self, db, helpers)
      local subschema = kong.db.plugins.schema.subschemas[self.params.name]
      if not subschema then
        return kong.response.exit(404, { message = "No plugin named '"
                                  .. self.params.name .. "'" })
      end

      local copy = api_helpers.schema_to_jsonable(subschema)
      strip_foreign_schemas(copy.fields)
      return kong.response.exit(200, copy)
    end
  },
  ["/schemas/filters/:name"] = {
    GET = function(self, db)
      local name = self.params.name

      if not wasm.filters_by_name[name] then
        local msg = "Filter '" .. name .. "' not found"
        return kong.response.exit(404, { message = msg })
      end

      local schema = wasm.filter_meta[name]
                 and wasm.filter_meta[name].config_schema
                  or default_filter_config_schema(db)

      return kong.response.exit(200, schema)
    end
  },
  ["/timers"] = {
    GET = function (self, db, helpers)
      local body = {
        worker = {
          id = ngx.worker.id() or -1,
          count = ngx.worker.count(),
        },
        stats = kong.timer:stats({
          verbose = true,
          flamegraph = true,
        })
      }
      return kong.response.exit(200, body)
    end
  },
  ["/status/dns"] = {
    GET = function (self, db, helpers)
      if not kong.configuration.new_dns_client then
        return kong.response.exit(501, { message = "not implemented with the legacy DNS client" })
      end

      return kong.response.exit(200, {
        worker = {
          id = ngx.worker.id() or -1,
          count = ngx.worker.count(),
        },
        stats = kong.dns.stats(),
      })
    end
  },
}
