local https_server = {}
https_server.__index = https_server


local fmt = string.format
local mock_srv_tpl_file = require "spec.fixtures.mock_webserver_tpl"
local ngx = require "ngx"
local pl_dir = require "pl.dir"
local pl_file = require "pl.file"
local pl_template = require "pl.template"
local pl_path = require "pl.path"
local uuid = require "resty.jit-uuid"
local http_client = require "resty.http"
local cjson = require "cjson"
local shell = require "resty.shell"


local Template = require("pl.stringx").Template


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


local HTTPS_SERVER_START_MAX_RETRY = 10

local tmp_root = os.getenv("TMPDIR") or "/tmp"
local host_regex = [[([a-z0-9\-._~%!$&'()*+,;=]+@)?([a-z0-9\-._~%]+|\[[a-z0-9\-._~%!$&'()*+,;=:]+\])(:?[0-9]+)*]]



local function create_temp_dir(copy_cert_and_key)
  local tmp_name = fmt("nginx_%s", uuid())
  local tmp_path = fmt("%s/%s", tmp_root, tmp_name)
  local _, err = pl_path.mkdir(tmp_path)
  if err then
    return nil, err
  end

  local _, err = pl_path.mkdir(tmp_path .. "/logs")
  if err then
    return nil, err
  end

  if copy_cert_and_key then
    local status = pl_dir.copyfile("./spec/fixtures/kong_spec.crt", tmp_path)
    if not status then
      return nil, "could not copy cert"
    end

    status = pl_dir.copyfile("./spec/fixtures/kong_spec.key", tmp_path)
    if not status then
      return nil, "could not copy private key"
    end
  end

  return tmp_path
end


local function create_conf(params)
  local tpl, err = pl_template.compile(mock_srv_tpl_file)
  if err then
    return nil, err
  end

  local compiled_tpl = Template(tpl:render(params, { ipairs = ipairs }))
  local conf_filename = params.base_path .. "/nginx.conf"
  local conf, err = io.open (conf_filename, "w")
  if err then
    return nil, err
  end

  conf:write(compiled_tpl:substitute(params))
  conf:close()

  return conf_filename
end


local function count_results(logs_dir)
  local results = {
    ["ok"] = 0,
    ["fail"] = 0,
    ["total"] = 0,
    ["status_ok"] = 0,
    ["status_fail"] = 0,
    ["status_total"] = 0
  }
  local error_log_filename = logs_dir .. "/error.log"

  for line in io.lines(error_log_filename) do
    local m = ngx.re.match(line, [[^.*\[COUNT\] (.+) (\d\d\d)\,.*\, host: \"(.+)\"$]])
    if m then
      local location = m[1]
      local status = m[2]
      local host = m[3]
      if host then
        local host_no_port = ngx.re.match(m[3], host_regex)
        if host_no_port then
          host = host_no_port[2]
        end
      else
        host = "nonamehost"
      end
      if results[host] == nil then
        results[host] = {
          ["ok"] = 0,
          ["fail"] = 0,
          ["status_ok"] = 0,
          ["status_fail"] = 0,
        }
      end

      if location == "slash" then
        if status == "200" then
          results.ok = results.ok + 1
          results[host].ok = results[host].ok + 1
        else
          results.fail = results.fail + 1
          results[host].fail = results[host].fail + 1
        end
        results.total = results.ok + results.fail
      elseif location == "status" then
        if status == "200" then
          results.status_ok = results.status_ok + 1
          results[host].status_ok = results[host].status_ok + 1
        else
          results.status_fail = results.status_fail + 1
          results[host].status_fail = results[host].status_fail + 1
        end
        results.status_total = results.status_ok + results.status_fail
      end
    end
  end

  return results
end


function https_server.clear_access_log(self)
  local client = assert(http_client.new())

  local uri = string.format("%s://%s:%d/clear_log", self.protocol, self.host, self.http_port)

  local res = assert(client:request_uri(uri, {
    method = "GET"
  }))

  assert(res.body == "cleared\n")
end


function https_server.get_access_log(self)
  local client = assert(http_client.new())

  local uri = string.format("%s://%s:%d/log?do_not_log", self.protocol, self.host, self.http_port)

  local res = assert(client:request_uri(uri, {
    method = "GET"
  }))

  return assert(cjson.decode(res.body))
end


function https_server.start(self)
  if not pl_path.exists(tmp_root) or not pl_path.isdir(tmp_root) then
    error("could not get a temporary path", 2)
  end

  local err
  self.base_path, err = create_temp_dir(self.protocol == "https")
  if err then
    error(fmt("could not create temp dir: %s", err), 2)
  end

  local conf_params = {
    base_path = self.base_path,
    delay = self.delay,
    cert_path = "./",
    check_hostname = self.check_hostname,
    logs_dir = self.logs_dir,
    host = self.host,
    hosts = self.hosts,
    http_port = self.http_port,
    protocol = self.protocol,
    worker_num = self.worker_num,
    disable_ipv6 = self.disable_ipv6,
  }

  local file, err = create_conf(conf_params)
  if err then
    error(fmt("could not create conf: %s", err), 2)
  end

  for _ = 1, HTTPS_SERVER_START_MAX_RETRY do
    if shell.run("nginx -c " .. file .. " -p " .. self.base_path, nil, 0) then
      return
    end

    ngx.sleep(1)
  end

  error("failed starting nginx")
end


function https_server.shutdown(self)
  local pid_filename = self.base_path .. "/logs/nginx.pid"
  local pid_file = io.open (pid_filename, "r")
  if pid_file then
    local pid, err = pid_file:read()
    if err then
      error(fmt("could not read pid file: %s", tostring(err)), 2)
    end

    local kill_nginx_cmd = fmt("kill -s TERM %s", tostring(pid))
    local status = shell.run(kill_nginx_cmd, nil, 0)
    if not status then
      error(fmt("could not kill nginx test server. %s was not removed", self.base_path), 2)
    end

    local pidfile_removed
    local watchdog = 0
    repeat
      pidfile_removed = pl_file.access_time(pid_filename) == nil
      if not pidfile_removed then
        ngx.sleep(0.01)
        watchdog = watchdog + 1
        if(watchdog > 100) then
          error("could not stop nginx", 2)
        end
      end
    until(pidfile_removed)
  end

  local count, err = count_results(self.base_path .. "/" .. self.logs_dir)
  if err then
    -- not a fatal error
    print(fmt("could not count results: %s", tostring(err)))
  end

  local _, err = pl_dir.rmtree(self.base_path)
  if err then
    print(fmt("could not remove %s: %s", self.base_path, tostring(err)))
  end

  return count
end

-- **DEPRECATED**: please use `spec.helpers.http_mock` instead.
function https_server.new(port, hostname, protocol, check_hostname, workers, delay, disable_ipv6)
  local self = setmetatable({}, https_server)
  local host
  local hosts

  if type(hostname) == "table" then
    hosts = hostname
    host = ""
    for _, h in ipairs(hostname) do
      host = fmt("%s %s", host, h)
    end
  else
    hosts = {hostname}
    host = hostname
  end

  self.check_hostname = check_hostname or false
  self.delay = tonumber(delay) or 0
  self.host = host or "localhost"
  self.hosts = hosts
  self.http_port = port
  self.logs_dir = "logs"
  self.protocol = protocol or "http"
  self.worker_num = workers or 2
  self.disable_ipv6 = disable_ipv6

  return self
end

return https_server
