-- This software is copyright Kong Inc. and its licensors.
-- Use of the software is subject to the agreement between your organization
-- and Kong Inc. If there is no such agreement, use is governed by and
-- subject to the terms of the Kong Master Software License Agreement found
-- at https://konghq.com/enterprisesoftwarelicense/.
-- [ END OF LICENSE 0867164ffc95e54f04670b5169c09574bdbd9bba ]

local stress_generator = {}
stress_generator.__index = stress_generator


local cjson = require "cjson"
local pl_file = require "pl.file"
local pl_path = require "pl.path"
local uuid = require "resty.jit-uuid"


local fmt = string.format
local tmp_root = os.getenv("TMPDIR") or "/tmp"


-- we need this to get random UUIDs
math.randomseed(os.time())


local attack_cmds = {
  ["http"] = "GET http://%s:%d%s",
}


function stress_generator.is_running(self)
  if self.finish_time == nil or self.finish_time <= os.time() then
    return false
  end

  return true
end


function stress_generator.get_results(self)
  if self.results ~= nil then
    return self.results
  end

  if self.results_filename == nil then
    return nil, "stress_generator was not run yet"
  end

  if stress_generator:is_running() then
    return nil, "stress_generator results not available yet"
  end

  local report_cmd = fmt("vegeta report -type=json %s 2>&1", self.results_filename)
  local report_pipe = io.popen(report_cmd)
  local output = report_pipe:read('*all')
  report_pipe:close()

  if pl_path.exists(self.results_filename) then
    pl_file.delete(self.results_filename)
  end


  local vegeta_results = cjson.decode(output)
  local results = {
    ["successes"] = 0,
    ["remote_failures"] = 0,
    ["proxy_failures"] = 0,
    ["failures"] = 0,
  }

  vegeta_results.status_codes = vegeta_results.status_codes or {}

  for status, count in pairs(vegeta_results.status_codes) do
    if status == "200" then
      results.successes = count
    elseif status == "502" or status == "504" then
      results.remote_failures = results.remote_failures + count
      results.failures = results.failures + count
    elseif status == "500" or status == "503" then
      results.proxy_failures = results.proxy_failures + count
      results.failures = results.failures + count
    else
      results.failures = results.failures + count
    end
  end

  self.results = results

  if self.debug then
    -- show pretty results
    local report_cmd = fmt("vegeta report %s 2>&1", self.results_filename)
    local report_pipe = io.popen(report_cmd)
    local output = report_pipe:read('*all')
    report_pipe:close()
    print(output)
  end

  return self.results
end


function stress_generator.run(self, uri, headers, duration, rate)
  if stress_generator:is_running() then
    return nil, "already running"
  end

  self.results_filename = fmt("%s/vegeta_%s", tmp_root, uuid())

  duration = duration or 1
  rate = rate or 100
  local attack_cmd = fmt(attack_cmds[self.protocol], self.host, self.port, uri)
  local req_headers = ""

  for name, value in pairs(headers) do
    req_headers = fmt("-header=%s:%s %s", name, value, req_headers)
  end

  local vegeta_cmd = fmt(
    "echo %s | vegeta attack %s -rate=%d -duration=%ds -workers=%d -timeout=5s -output=%s",
    attack_cmd, req_headers, rate, duration, self.workers, self.results_filename)

  self.pipe = io.popen(vegeta_cmd)
  -- we will rely on vegeta's duration
  self.finish_time = os.time() + duration
end


function stress_generator.new(protocol, host, port, workers, debug)
  if io.popen == nil then
    error("stress_generator is not supported in this platform", 2)
  end

  local self = setmetatable({}, stress_generator)

  protocol = protocol or "http"

  if protocol ~= "http" then
    error("stress_generator supports only http")
  end

  self.debug = debug == true
  self.host = host or "127.0.0.1"
  self.port = port or "80"
  self.protocol = protocol
  self.workers = workers or 10

  return self
end


return stress_generator
