local ngx_re_split = require("ngx.re").split
local string_to_bool = require("util").string_to_bool

local certificate_configured_for_current_request =
  require("certificate").configured_for_current_request

local ngx = ngx
local io = io
local math = math
local string = string
local original_randomseed = math.randomseed
local string_format = string.format
local ngx_redirect = ngx.redirect

local _M = {}

local seeds = {}
-- general Nginx configuration passed by controller to be used in this module
local config

local function get_seed_from_urandom()
  local seed
  local frandom, err = io.open("/dev/urandom", "rb")
  if not frandom then
    ngx.log(ngx.WARN, 'failed to open /dev/urandom: ', err)
    return nil
  end

  local str = frandom:read(4)
  frandom:close()
  if not str then
    ngx.log(ngx.WARN, 'failed to read data from /dev/urandom')
    return nil
  end

  seed = 0
  for i = 1, 4 do
      seed = 256 * seed + str:byte(i)
  end

  return seed
end

math.randomseed = function(seed)
  local pid = ngx.worker.pid()
  if seeds[pid] then
    ngx.log(ngx.WARN, string.format("ignoring math.randomseed(%d) since PRNG "
      .. "is already seeded for worker %d", seed, pid))
    return
  end

  original_randomseed(seed)
  seeds[pid] = seed
end

local function randomseed()
  local seed = get_seed_from_urandom()
  if not seed then
    ngx.log(ngx.WARN, 'failed to get seed from urandom')
    seed = ngx.now() * 1000 + ngx.worker.pid()
  end
  math.randomseed(seed)
end

local function redirect_to_https(location_config)
  if location_config.force_no_ssl_redirect then
    return false
  end

  if location_config.force_ssl_redirect and ngx.var.pass_access_scheme == "http" then
    return true
  end

  if ngx.var.pass_access_scheme ~= "http" then
    return false
  end

  return location_config.ssl_redirect and certificate_configured_for_current_request()
end

local function redirect_host()
  local host_port, err = ngx_re_split(ngx.var.best_http_host, ":")
  if err then
    ngx.log(ngx.ERR, "could not parse variable: ", err)
    return ngx.var.best_http_host;
  end

  return host_port[1];
end

local function parse_x_forwarded_host()
  local hosts, err = ngx_re_split(ngx.var.http_x_forwarded_host, ",")
  if err then
    ngx.log(ngx.ERR, string_format("could not parse variable: %s", err))
    return ""
  end

  return hosts[1]
end

function _M.init_worker()
  randomseed()
end

function _M.set_config(new_config)
  config = new_config
end

-- rewrite gets called in every location context.
-- This is where we do variable assignments to be used in subsequent
-- phases or redirection
function _M.rewrite()

  local location_config = {
    force_ssl_redirect = string_to_bool(ngx.var.force_ssl_redirect),
    ssl_redirect = string_to_bool(ngx.var.ssl_redirect),
    force_no_ssl_redirect = string_to_bool(ngx.var.force_no_ssl_redirect),
    preserve_trailing_slash = string_to_bool(ngx.var.preserve_trailing_slash),
    use_port_in_redirects = string_to_bool(ngx.var.use_port_in_redirects),
  }

  ngx.var.pass_access_scheme = ngx.var.scheme

  ngx.var.best_http_host = ngx.var.http_host or ngx.var.host

  if config.use_forwarded_headers then
    -- trust http_x_forwarded_proto headers correctly indicate ssl offloading
    if ngx.var.http_x_forwarded_proto then
      ngx.var.pass_access_scheme = ngx.var.http_x_forwarded_proto
    end

    if ngx.var.http_x_forwarded_port then
      ngx.var.pass_server_port = ngx.var.http_x_forwarded_port
    end

    -- Obtain best http host
    if ngx.var.http_x_forwarded_host then
      ngx.var.best_http_host = parse_x_forwarded_host()
    end
  end

  if config.use_proxy_protocol then
    if ngx.var.proxy_protocol_server_port == "443" then
      ngx.var.pass_access_scheme = "https"
    end
  end

  ngx.var.pass_port = ngx.var.pass_server_port
  if config.is_ssl_passthrough_enabled then
    if ngx.var.pass_server_port == config.listen_ports.ssl_proxy then
      ngx.var.pass_port = 443
    end
  elseif ngx.var.pass_server_port == config.listen_ports.https then
    ngx.var.pass_port = 443
  end

  if redirect_to_https(location_config) then
    local request_uri = ngx.var.request_uri
    -- do not append a trailing slash on redirects unless enabled by annotations
    if location_config.preserve_trailing_slash == false then
      if string.byte(request_uri, -1, -1) == string.byte('/') then
        request_uri = string.sub(request_uri, 1, -2)
      end
    end

    local uri = string_format("https://%s%s", redirect_host(), request_uri)

    if location_config.use_port_in_redirects then
      uri = string_format("https://%s:%s%s", redirect_host(),
        config.listen_ports.https, request_uri)
    end

    return ngx_redirect(uri, config.http_redirect_code)
  end

end

function _M.header()
  if config.hsts and ngx.var.scheme == "https" and certificate_configured_for_current_request then
    local value = "max-age=" .. config.hsts_max_age
    if config.hsts_include_subdomains then
      value = value .. "; includeSubDomains"
    end
    if config.hsts_preload then
      value = value .. "; preload"
    end
    ngx.header["Strict-Transport-Security"] = value
  end
end

return _M
