local _M = require('apicast.policy').new('Metrics', 'builtin')

local resty_env = require('resty.env')
local errlog = require('ngx.errlog')
local prometheus = require('apicast.prometheus')
local metrics_updater = require('apicast.metrics.updater')
local tonumber = tonumber
local select = select
local find = string.find
local pairs = pairs

-- extended_metrics is a variable used to report multiple labels in some
-- metrics. This can be useful in small environements, but can be problematic
-- for large users due this can create a large matrix of metrics.
-- More info about this can be found in Prometheus doc:
-- https://prometheus.io/docs/practices/naming/#labels

local extended_metrics = resty_env.enabled('APICAST_EXTENDED_METRICS')

local upstream_metrics = require('apicast.metrics.upstream')

local new = _M.new

local log_levels_list = {
  'emerg',
  'alert',
  'crit',
  'error',
  'warn',
  'notice',
  'info',
  'debug',
}

local log_level_env = 'NGINX_METRICS_LOG_LEVEL'
local max_logs_env = 'NGINX_METRICS_MAX_LOGS'

local log_level_default = 'error'
local max_logs_default = 100

local function find_i(t, value)
  for i=1, #t do
    if t[i] == value then return i end
  end
end

local empty = {}

local function get_logs(max)
  return errlog.get_logs(max) or empty
end

local function filter_level()
  local level = resty_env.value(log_level_env) or log_level_default

  local level_index = find_i(log_levels_list, level)

  if not level_index then
    ngx.log(ngx.WARN, _M._NAME, ': invalid level: ', level, ' using error instead')
    level_index = find_i(log_levels_list, 'error')
  end

  return level_index
end

function _M.new(configuration)
  local m = new()

  local config = configuration or empty

  -- how many logs to take in one iteration
  m.max_logs = tonumber(config.max_logs) or
               resty_env.value(max_logs_env) or
               max_logs_default

  return m
end

local logs_metric = prometheus('counter', 'nginx_error_log', "Items in nginx error log", {'level'})
local http_connections_metric =  prometheus('gauge', 'nginx_http_connections', 'Number of HTTP connections', {'state'})
local shdict_capacity_metric = prometheus('gauge', 'openresty_shdict_capacity', 'OpenResty shared dictionary capacity', {'dict'})
local shdict_free_space_metric = prometheus('gauge', 'openresty_shdict_free_space', 'OpenResty shared dictionary free space', {'dict'})
local apicast_status_metric = prometheus('counter', "apicast_status", "HTTP status generated by APIcast", {"status"})
local worker_process_metric  = prometheus('counter', "worker_process", "Number of times that a nginx worker has been started", {})

local response_times = prometheus(
  'histogram',
  'total_response_time_seconds',
  'Time needed to send a response to the client (in seconds).',
  { 'service_id', 'service_system_name' }
)

function _M.init_worker()
    metrics_updater.inc(worker_process_metric)
end

function _M.init()
  errlog.set_filter_level(filter_level())

  get_logs(100) -- to throw them away after setting the filter level (and get rid of debug ones)

  for name,dict in pairs(ngx.shared) do
    metrics_updater.set(shdict_capacity_metric, dict:capacity(), name)
  end
end

local status_map = setmetatable({
  -- http://mailman.nginx.org/pipermail/nginx/2013-May/038773.html
  [9] = 499,
}, { __index = function(_, k) return tonumber(k) end })

function _M:metrics()
  local logs = get_logs(self.max_logs)

  for i = 1, #logs, 3 do
    metrics_updater.inc(logs_metric, log_levels_list[logs[i]] or 'unknown')
  end

  local response = ngx.location.capture("/nginx_status")

  if response.status == 200 then
    local accepted, handled, total = select(3, find(response.body, [[accepts handled requests%s+(%d+) (%d+) (%d+)]]))
    local var = ngx.var

    metrics_updater.set(http_connections_metric, var.connections_reading, 'reading')
    metrics_updater.set(http_connections_metric, var.connections_waiting, 'waiting')
    metrics_updater.set(http_connections_metric, var.connections_writing, 'writing')
    metrics_updater.set(http_connections_metric, var.connections_active, 'active')
    metrics_updater.set(http_connections_metric, accepted, 'accepted')
    metrics_updater.set(http_connections_metric, handled, 'handled')
    metrics_updater.set(http_connections_metric, total, 'total')
  else
    prometheus:log_error('Could not get status from nginx')
  end

  for name,dict in pairs(ngx.shared) do
    metrics_updater.set(shdict_free_space_metric, dict:free_space(), name)
  end
end

local function report_req_response_time(service)
  -- Use ngx.var.original_request_time instead of ngx.var.request_time so
  -- the time spent in the post_action phase is not taken into account.
  local resp_time = tonumber(ngx.var.original_request_time)
  if resp_time and response_times then
    response_times:observe(resp_time, {
      service.id or "",
      service.system_name or ""
    })
  end
end

function _M.log(_, context)
  local service = { id = "", system_name = "" }
  if context.service and extended_metrics then
    service = context.service
  end
  upstream_metrics.report(ngx.var.upstream_status, ngx.var.upstream_response_time, service)
  report_req_response_time(service)
  metrics_updater.inc(apicast_status_metric, status_map[ngx.status])
end

return _M
