--[[
	ATENCION:
		Se utiliza "ssl.https" en lugar "socket.http" para poder realizar request "https",
		ya que la versión 4.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 "json"
local ltn12 = require "ltn12"
require "socket"
require "uuid"
local TOTAL_RETRIES = 3
local TIMEOUT_SEC = 1

module(..., package.seeall)

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

--[[
Keys programación (cuenta tito).
----
6c0aa9ff967b4833ad3018557272547e
867844dda8ac48a28d15cb31bce82c01
]]--
local function AddSubscriptionKeyToHeader (headers, ocpApimSubscriptionKey)
	assert(headers)
	assert(ocpApimSubscriptionKey)
	--LogDebug("ocpApimSubscriptionKey: " .. tostring(ocpApimSubscriptionKey))
	headers["Ocp-Apim-Subscription-Key"] = ocpApimSubscriptionKey
end

local function MakeBasicHeader (ocpApimSubscriptionKey, myHeaders)
	local headers = {}
	headers = myHeaders or AddSubscriptionKeyToHeader(headers, ocpApimSubscriptionKey) or {}
	return headers
end

local function MakeContentTypeHeader (body, contentType, ocpApimSubscriptionKey, myHeaders)
	body = body or {}
	headers = myHeaders or MakeBasicHeader(ocpApimSubscriptionKey)
	local encodedBody
	if contentType then
		headers["Content-Type"] = contentType
		encodedBody = body
	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("[QNA_MAKER.REQUEST:ExecuteRequest] Waiting %d seconds.", TIMEOUT_SEC)
			socket.sleep(TIMEOUT_SEC)
		end
	end

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

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

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

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

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

		--LogDebug("[QNA_MAKER.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, ocpApimSubscriptionKey, headers)
	body = body or {}
	local headers, encodedBody = MakeContentTypeHeader(body, contentType, ocpApimSubscriptionKey, headers)

	local function Request (retry)
		LogInfo("[QNA_MAKER.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)
		})
		--LogDebug("[QNA_MAKER.REQUEST:Post] POST 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 Delete (uri, body, contentType, ocpApimSubscriptionKey, headers)
	body = body or {}
	local headers, encodedBody = MakeContentTypeHeader(body, contentType, ocpApimSubscriptionKey, headers)

	local function Request (retry)
		LogInfo("[QNA_MAKER.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("[QNA_MAKER.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, ocpApimSubscriptionKey, headers)
	body = body or {}
	local headers, encodedBody = MakeContentTypeHeader(body, contentType, ocpApimSubscriptionKey, headers)

	local function Request (retry)
		LogInfo("[QNA_MAKER.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("[QNA_MAKER.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, ocpApimSubscriptionKey, headers)
	body = body or {}
	local headers, encodedBody = MakeContentTypeHeader(body, contentType, ocpApimSubscriptionKey, headers)

	local function Request (retry)
		LogInfo("[QNA_MAKER.REQUEST:Patch] PATCH 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 = "PATCH",
			headers = headers,
			source = ltn12.source.string(encodedBody),
			--respuesta
			sink = ltn12.sink.table(response)
		})

		--LogDebug("[QNA_MAKER.REQUEST:Patch] PATCH 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 CurlPost (uri, body, contentType, ocpApimSubscriptionKey, headers)
	body = body or {}
	local headers, encodedBody = MakeContentTypeHeader(body, contentType, ocpApimSubscriptionKey, headers)

	LogInfo("[QNA_MAKER.REQUEST:CurlPost] POST Request URI: '%s', headers: '%s', body: '%s'", uri, json.encode(headers), encodedBody)

	local temporalFullFileName = "/tmp/" .. uuid.new() .. ".txt"
	local command = string.format([[curl -X POST -k -H 'Content-type: %s' -H 'Authorization: %s' -d '%s' %s > %s]],
		headers["Content-Type"],
		headers["Authorization"],
		encodedBody,
		uri,
		temporalFullFileName
	)

	local commandResponse = os.execute(command)
	if commandResponse ~= 0 then
		return false, {errType = "UNABLE TO EXECUTE CURL COMMAND", command = command, commandResponse = commandResponse}
	end

	local content = {}
	local f = io.open(temporalFullFileName)
	if not f then
		return false, {errType = "UNABLE TO OPEN CURL RESULT"}
	end
	local k = 1
	for line in f:lines() do
		content[k] = line
		k = k + 1
	end
	f:close()
	os.execute("rm " .. temporalFullFileName)

	local encodedResponse = content[1]
	local ok, decodedResponse = pcall(json.decode, encodedResponse)
	if not ok or not decodedResponse then
		return false, {errType = "INVALID CURL RESPONSE", response = encodedResponse}
	end

	-- Por compatibilidad con la rocka SSL, se debe de retornar de la misma manera.
	-- Es decir, tiene que retornar una tabla con 1 un ítem de tipo json string.
	local response = {}
	table.insert(response, encodedResponse)

	return true, response
end