--[[
	ATENCION:
		Se utiliza "ssl.https" en lugar "socket.http" para poder realizar request "https",
		ya que la versión 2.0 del API AZURE, solo soporta "https"
]]--
local Synchronize = require "Synchronize"
local https = Synchronize("luasec_mutex", function()
	return require ("ssl.https")
end)
local json = require "cjson"
local ltn12 = require "ltn12"
require "socket"
local TOTAL_RETRIES = 3
local TIMEOUT_SEC = 1

module(..., package.seeall)

-- *******************************************************************************
-- API REQUEST (GET, POST, PUT, DELETE, PATCH)
-- *******************************************************************************

--[[
azures keys:

d24518b086a248c4ae64afd6e3eb475c
f5c41ac5c06b46d6a697c076f4f3923c

]]--
function GetDefaultDemoSubscriptionKey ()
	-- Demo - Tito (Ocp-Apim-Subscription-Key)
	return "9db3499edaf549fc951f2b15ba0084e8"
end

local function AddSubscriptionKeyToHeader (headers)
	assert(headers)
	headers["Ocp-Apim-Subscription-Key"] = GetDefaultDemoSubscriptionKey() --"9db3499edaf549fc951f2b15ba0084e8" -- Tito
	--headers["Ocp-Apim-Subscription-Key"] = "08bf72c2f5e74ee8b7e5148692fb2f03" -- Cany 
end

local function MakeBasicHeader (additionalHeaders)
	local headers = {}
	AddSubscriptionKeyToHeader(headers)
	for name, value in pairs(additionalHeaders or {}) do
		headers[name] = value
	end
	return headers
end

local function MakeContentTypeHeader (body, contentType, additionalHeaders)
	body = body or {}
	local headers = MakeBasicHeader(additionalHeaders)
	local encodedBody
	if contentType then
		headers["Content-Type"] = contentType		
		if type(body) == "table" then
			encodedBody = json.encode(body)
		else
			encodedBody = body
		end
	else
		if type(body) == "table" then
			headers["Content-Type"] = "application/json"
			encodedBody = json.encode(body)
		else
			headers["Content-Type"] = "text/plain"
			encodedBody = body
		end
	end
	headers["Content-Length"] = #encodedBody
	return headers, encodedBody
end

local function GetError (err)
	assert(type(err) == "table")
	local encodedErr = err[1]
	if type(encodedErr) == "string" then
		local ok, decodedErr = pcall(json.decode, encodedErr)
		if not ok or not decodedErr then
			return {}
		end
		return decodedErr
	end
	return err
end

local function ExecuteRequest (RequestFn)
	local result, code, header, statusline
	local response = {}
	local retry = 0

	while retry < TOTAL_RETRIES do
		retry = retry + 1
		result, code, header, statusline, response = RequestFn(retry)
		--[[
			El código de error (429 Too Many Requests) se da lo siguiente:
				Se han enviado demasiadas peticiones en un tiempo determinado. Es un servidor que le dice que por favor deje de enviar peticiones.
				Entonces realizamos una pausa y volvemos a reintentar y no más de 3 veces.
		--]]
		if code ~= 429 then
			break
		elseif code == 429 then
			LogInfo("[REQUEST:ExecuteRequest] Waiting %d seconds.", TIMEOUT_SEC)
			socket.sleep(TIMEOUT_SEC)
		end
	end

	-- OK o CREATED o ACCEPTED
	if code == 200 or code == 201 or code == 202 then
		--LogDebug("[REQUEST:ExecuteRequest] request CODE: '%s' URI: '%s'\nresponse: '%s'.", code, uri, response)
		return true, response, header 
	end

	local err = GetError(response)
	return false, {code = code, header = header, statusline = statusline, response = err}
end

------------------------------
--[[
	https://gist.github.com/liukun/f9ce7d6d14fa45fe9b924a3eed5c3d99
]]--
local CharToHex = function(c)
	return string.format("%%%02X", string.byte(c))
end

function UrlEncode (url)
	assert(type(url) == "string")
	url = url:gsub("\n", "\r\n")
	url = url:gsub("([^%w ])", CharToHex)
	url = url:gsub(" ", "+")
	return url
end

local HexToChar = function(x)
	return string.char(tonumber(x, 16))
end

function UrlDecode (url)
	assert(type(url) == "string")
	url = url:gsub("+", " ")
	url = url:gsub("%%(%x%x)", HexToChar)
	return url
end
------------------------------

function Get (uri, additionalHeaders)
	local headers = MakeBasicHeader(additionalHeaders)

	local function Request (retry)
		LogInfo("[REQUEST:Get] GET Request retry %d of %d. URI: '%s', headers: '%s'.", retry, TOTAL_RETRIES, uri, json.encode(headers))

		local responseBody = {}
		local result, code, header, statusline = https.request({
			url = uri,
			method = "GET",
			headers = headers,
			--respuesta
			sink = ltn12.sink.table(responseBody)
		})

		local response_str = table.concat(responseBody, "")

		local response = {}
		response_str:gsub(".",function(c) table.insert(response,c) end)

		LogDebug("[REQUEST:Get] GET request result: '%s', code: '%s' headers: '%s', statusline: '%s', response: '%s'", result, code, json.encode(headers or {}), statusline, response)

		return result, code, header, statusline, response
	end

	return ExecuteRequest(Request)
end

function Post (uri, body, contentType, additionalHeaders)
	body = body or {}
	local headers, encodedBody = MakeContentTypeHeader(body, contentType, additionalHeaders)

	local function Request (retry)
		LogInfo("[REQUEST:Post] POST Request retry %d of %d. URI: '%s', headers: '%s', body: '%s'", retry, TOTAL_RETRIES, uri, json.encode(headers), encodedBody)

		local response = {}
		local result, code, header, statusline = https.request({
			url = uri,
			method = "POST",
			headers = headers,
			source = ltn12.source.string(encodedBody),
			--respuesta
			sink = ltn12.sink.table(response)
		})

		return result, code, header, statusline, response
	end

	return ExecuteRequest(Request)
end

function Delete (uri, body, contentType, additionalHeaders)
	body = body or {}
	local headers, encodedBody = MakeContentTypeHeader(body, contentType, additionalHeaders)

	local function Request (retry)
		LogInfo("[REQUEST:Delete] DELETE Request retry %d of %d. URI: '%s', headers: '%s', body: '%s'", retry, TOTAL_RETRIES, uri, json.encode(headers), encodedBody)

		local response = {}

		local result, code, header, statusline = https.request({
			url = uri,
			method = "DELETE",
			headers = headers,
			source = ltn12.source.string(encodedBody),
			--respuesta
			sink = ltn12.sink.table(response)
		})

		--LogDebug("[REQUEST:Delete] DELETE request result: '%s', code: '%s' headers: '%s' , statusline: '%s', response: '%s'", result, code, json.encode(headers or {}), statusline, response)

		return result, code, header, statusline, response
	end

	return ExecuteRequest(Request)
end

function Put (uri, body, contentType, additionalHeaders)
	body = body or {}
	local headers, encodedBody = MakeContentTypeHeader(body, contentType, additionalHeaders)

	local function Request (retry)
		LogInfo("[REQUEST:Put] PUT retry %d of %d. Request URI: '%s', headers: '%s', body: '%s'", retry, TOTAL_RETRIES, uri, json.encode(headers), encodedBody)

		local response = {}

		local result, code, header, statusline = https.request({
			url = uri,
			method = "PUT",
			headers = headers,
			source = ltn12.source.string(encodedBody),
			--respuesta
			sink = ltn12.sink.table(response)
		})

		--LogDebug("[REQUEST:Put] PUT request result: '%s', code: '%s' headers: '%s' , statusline: '%s', response: '%s'", result, code, json.encode(headers or {}), statusline, response)

		return result, code, header, statusline, response
	end

	return ExecuteRequest(Request)
end

function Patch (uri, body, contentType, additionalHeaders)
	body = body or {}
	local headers, encodedBody = MakeContentTypeHeader(body, contentType, additionalHeaders)

	local function Request (retry)
		LogInfo("[REQUEST:Patch] PATCH retry %d of %d. Request URI: '%s', headers: '%s', body: '%s'", retry, TOTAL_RETRIES, uri, json.encode(headers), json.encode(encodedBody))

		local response = {}

		local result, code, header, statusline = https.request({
			url = uri,
			method = "PATCH",
			headers = headers,
			source = ltn12.source.string(encodedBody),
			--respuesta
			sink = ltn12.sink.table(response)
		})

		LogDebug("[REQUEST:Patch] PATCH request result: '%s', code: '%s' headers: '%s' , statusline: '%s', response: '%s'", result, code, json.encode(headers or {}), statusline, json.encode(response))

		return result, code, header, statusline, response
	end

	return ExecuteRequest(Request)
end
