local cookie = require('resty.cookie')
local digest = require('openssl').digest
local pbi = require "ws_message_pb"
local splashscreen_sessions = ngx.shared.splashscreen_sessions
local cookie_name = ngx.var.ws_cookie_name
local i360_socket = "unix:/var/run/defence360agent/protobuf.sock"


local function write_format(little_endian, format, ...)
  local res = ''
  local values = {...}
  for i=1,#format do
    local size = tonumber(format:sub(i,i))
    local value = values[i]
    local str = ""
    for j=1,size do
      str = str .. string.char(value % 256)
      value = math.floor(value / 256)
    end
    if not little_endian then
      str = string.reverse(str)
    end
    res = res .. str
  end
  return res
end


local function composeMessage(uid, ip, success)
    local msg = pbi.SendInfo()
    msg.ip = ip
    msg.method = msg.WEBSHIELD
    msg.timestamp = os.time(os.date("!*t"))

    if uid then   -- yes, UID might be absent
        msg.websh.user_id = uid
    end

    if success then
        msg.websh.captcha = msg.websh.PASSED
    else
        msg.websh.captcha = msg.websh.FAILED
    end

    local serialized = msg:SerializeToString()
    return write_format(false, "2", string.len(serialized)) .. serialized
end


local function notify_peer (uid, ip, success)

    local sock = ngx.socket.tcp()
    sock:settimeout(1000)
    local ok, err = sock:connect(i360_socket)

    if not ok then
        ngx.log(ngx.ERR, "Could not connect to i360 socket: ", err)
        return
    end

    local sent, err = sock:send(composeMessage(uid, ip, success))

    if not sent then
        ngx.log(ngx.ERR, "Could not send to i360 socket: ", err)
    end

    sock:close()
end


local function set_uid_cookie()
    local cookie_obj, err = cookie:new()
    if not cookie_obj then
        ngx.log(ngx.ERR, "Failed to init cookie: ", err)
        return
    end

    local user_agent = ngx.var.http_user_agent
    if user_agent == nil then
        user_agent = ''
    end
    local user_data = ngx.var.wsuserip .. user_agent
    local secret_key = ngx.var.ws_trusted_secret_key
    local ttl = tostring(ngx.time() + tonumber(ngx.var.ws_cookie_ttl))
    local hashsum = digest.digest('sha1', user_data .. secret_key .. ttl, false)
    local val = hashsum .. "." .. ttl .. ".1"

    local current_timestamp = os.time()
    local offset = 30 * 24 * 3600     -- 30 days
    local expiration_date = os.date(
        "!%a, %d-%b-%y %H:%M:%S GMT", current_timestamp + offset)

    local ok, err = cookie_obj:set({
        key = cookie_name,
        domain = ngx.var.host,
        samesite = "Lax",
        httponly = true,
        value = val,
        path = "/",
        expires = expiration_date
    })
    if not ok then
        ngx.log(ngx.ERR, "Could not set cookie: ", err)
    end

    return hashsum
end


local function route()

    local chkval = ngx.var.arg_wsidchk

    if not chkval then                  -- no query arg with check sum
        ngx.exit(ngx.HTTP_BAD_REQUEST)  -- return 400
    end

    local chknum = tonumber(chkval)

    if not chknum then                  -- query arg is not number
        ngx.exit(ngx.HTTP_BAD_REQUEST)  -- return 400
    end

    local xsum = string.format("%X", chknum)                -- convert query arg to hex
    local redirect_uri = splashscreen_sessions:get(xsum)    -- and use it as key for dict lookup

    if redirect_uri then                                    -- key found, so we have location for redirection

        local userid = set_uid_cookie()                       -- set cookie
        local userip = ngx.var.wsuserip

        -- splashscreen had been shown instead of captcha
        if ip_status == "GRAY" then
            notify_peer(userid, userip, true)
        end

        return ngx.redirect(redirect_uri)                   -- and redirect
    end

    ngx.exit(ngx.HTTP_NOT_FOUND)                            -- key not found. So return 404
end

route()