local resty_cookie = require("resty.cookie")   -- https://github.com/cloudflare/lua-resty-cookie
local util = require("util")
local json = require("json")
local agent_pub = require("agent_pub")
local config = require("config")

local no_permission = "/wolf/rbac/no_permission"
local no_permission_html = "/wolf/rbac/no_permission.html"
local basic_auth_access_check_url = "/wolf/rbac/access_check"


function get_authorization()
    local headers = ngx.req.get_headers()
	local authorization = headers["Authorization"]
    return authorization
end

local function get_host_port()
    local host = ngx.var.host
    local port = ""
    if ngx.var.server_port and ngx.var.server_port ~= 80 then
        port = ":" .. tostring(ngx.var.server_port)
    end
    local host_port = (ngx.var.scheme or "http") .. "://" .. host .. port
    return host_port
end

local function url_args_as_args(ext_args)
	local args = ngx.req.get_uri_args()
    local host_port = get_host_port()

    local full_url = host_port .. ngx.var.uri
	args["return_to"] = full_url
	if ext_args and type(ext_args) == 'table' then
        for k, v in pairs(ext_args) do
			args[k] = v
		end
	end
	return args
end

local function check_url_permission(appID, action, resName, clientIP)
    local retry_max = 3
    local reason = nil;
    local userInfo = nil
	local res = nil
    for i = 1, retry_max do
        local args = { appID = appID, resName = resName, action = action, agentName=config.agentName, clientIP=clientIP}
        local url = basic_auth_access_check_url .. "?" .. ngx.encode_args(args)
        res = ngx.location.capture(url)
        if res then
            ngx.log(ngx.INFO, "check permission request:", url, ", status:", res.status, ",body:", tostring(res.body))
            if res.status < 500 then
                break
            else
                ngx.log(ngx.ERR, string.format("request [curl -v %s] failed! status:%d", url, res.status))
            end
        else
            reason = 'check permission failed, check request failed!'
            ngx.log(ngx.ERR, "fail request: ", url)
        end
    end
    if not res then
        return false, 500, reason
    end


    if res.status ~= 200 then
    	local strBody = util.trim(res.body or "")
        local body, err = json.loads(strBody)
	    if err then
            userInfo = res.body
            reason = 'check permission failed! parse response json failed!'
        else
            reason = body.reason
	    end

        return false, res.status, reason, userInfo, res.headers
    else
    	local body, err = json.loads(res.body)
	    if err then
            userInfo = res.body
            reason = 'check permission failed! parse response json failed!'
            ngx.log(ngx.ERR, "json.loads(", res.body, ") failed! err:", err)
        else
            userInfo = body.data.userInfo
            reason = body.reason
        end

    	return true, res.status, reason, userInfo, res.headers
    end
end


local function url_redirect(url, args)
    local appID = ngx.var.appID or "appIDUnset"
    args.appid = appID
    args = ngx.encode_args(args)
    util.redirect(url, args)
end

local function access_check()
    local url = ngx.var.uri
    local action = ngx.req.get_method()
	if util.url_in_ignore_list(url) then
		ngx.log(ngx.INFO, "check permission, ignore current request!")
		return
	end

    local appID = ngx.var.appID or "appIDUnset"
    local loginPrompt = ngx.var.loginPrompt or "input you username and password"
    local clientIP = util.clientIP()
    local permItem = "{appID: " .. appID .. ", action: " .. action .. ", url: " .. url .. ", clientIP: " .. clientIP .. "}"
	ngx.log(ngx.INFO, "Cookie: ", ngx.var.http_cookie, ", permItem=", permItem)

	local authorization = get_authorization()
	if authorization == nil then
		ngx.log(ngx.WARN, "no permission to access ", permItem, ", need login!")
        ngx.status = ngx.HTTP_UNAUTHORIZED
        ngx.header["WWW-Authenticate"] = "Basic realm=\"" .. loginPrompt .. "\""
        ngx.send_headers()
        ngx.flush(true)
	end

    local ok, status, reason, userInfo, headers = check_url_permission(appID, action, url, clientIP)
	ngx.log(ngx.INFO, " check_url_permission(", permItem, ",token=", tostring(token), ")=",
        ok, ", status:", tostring(status), ", userInfo:", tostring(json.dumps(userInfo)))

    local userID = -1
	local username = nil
    local nickname = nil
    if type(userInfo) == 'table' then
        ngx.req.set_header("X-UserId", userInfo.id)
        ngx.req.set_header("X-Username", userInfo.username)
        ngx.req.set_header("X-nickname", ngx.escape_uri(userInfo.nickname) or userInfo.username)
        -- local args = ngx.req.get_uri_args()
		ngx.ctx.userInfo = userInfo
        userID = userInfo.id
		username = userInfo.username
        nickname = userInfo.nickname
	end
	if headers and headers["Set-Cookie"] then
		local cookie_value = headers["Set-Cookie"]
		ngx.header['Set-Cookie'] = cookie_value
		ngx.log(ngx.INFO, "******* Re Set-Cookie:", cookie_value, " *******")
    end
	if ok then
		---
	else
		-- no permission.
		if status == ngx.HTTP_UNAUTHORIZED or status == ngx.HTTP_FORBIDDEN then
			if reason == "ERR_TOKEN_INVALID" then
                ngx.status = ngx.HTTP_UNAUTHORIZED
                ngx.header["WWW-Authenticate"] = "Basic realm=\"" .. loginPrompt .. "\""
                ngx.send_headers()
                ngx.flush(true)
            else
                local redirect_url = no_permission_html
                if url == '/' then
                    redirect_url = no_permission_html
                end
                url_redirect(redirect_url, { username = username, reason=reason })
            end
        elseif status == ngx.HTTP_BAD_REQUEST then
            ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
            ngx.header["Content-Type"] = "text/plain"
			ngx.send_headers()
			ngx.flush(true)
			ngx.say("rbac check permission failed! status:" .. tostring(status))
			ngx.flush(true)
		else
			ngx.status = status
            ngx.header["Content-Type"] = "text/plain"
			ngx.send_headers()
			ngx.flush(true)
			ngx.say("rbac check permission failed! status:" .. tostring(status))
			ngx.flush(true)
		end
	end
end

access_check()