local json = require "cjson"
local request = require "luis.request"
local luis_models = require("luis.models"):GetModels()
local utils = require "luis.utils"
require "socket" 

module(..., package.seeall)

local m_instanceOwner
function SetOwner (instanceOwner)
	m_instanceOwner = instanceOwner
end

local m_defaultApiVersion = "2022-10-01-preview"

local function GetBaseURI (id)
	id = id or "APPS"
	return m_instanceOwner:GetBaseURI(id)
end
local function MakeCompleteURI (uriBaseId, uri, apiVersion, ...)
	uriBaseId = uriBaseId or "APPS"
	return m_instanceOwner:MakeCompleteURI(m_defaultApiVersion, uriBaseId, uri, apiVersion, ...) 
end

local function MakePagingParameters (params)
	local api = require "luis"
	return api.MakePagingParameters(params)
end

function LoadAllAppIntents (appId, versionId, paginated, vcc) 
	assert(appId)
	assert(versionId)

	local resource_name, subscription_key_1 = GetLanguageStudioAccount(vcc)

	local uri = MakeCompleteURI("APPS", appId .."/:export?api-version=".. m_defaultApiVersion .. "&format=Conversation&stringIndexType=Utf16CodeUnit")
	uri = uri:gsub("resourcename", resource_name)

	LogInfo("[LUIS_APPS40:LoadAllAppIntents] uri: %s", uri)

	local additionalHeaders = {
		["Ocp-Apim-Subscription-Key"] = subscription_key_1
	}

	local success, response, headers = request.Post(uri, {}, "application/json; charset=UTF-8", additionalHeaders)
	if not success then
		LogError("[LUIS_APPS40:LoadAllAppIntents] LoadAllAppIntents Error: '%s'", json.encode(response))
		return false, response
	end

	-- luego obtengo el location
	local operationLocation = headers["operation-location"] 

	LogInfo("[LUIS_INTENTS:LoadAllAppIntents] operationLocation: %s", operationLocation)

	-- luego consulto N veces ese location

	local success = false
	local response = {}
	local retry = 0
	local TOTAL_RETRIES = 10
	local TIMEOUT_SEC = 3

	while retry < TOTAL_RETRIES do
		retry = retry + 1
		
		success, response = request.Get(operationLocation, additionalHeaders)

		if not success then
			LogError("[LUIS_INTENTS:LoadAllAppIntents] Error loading all APP COMPOSITE INTENTS on appId '%s' versionId: '%s'. Error: '%s'", appId, versionId, json.encode(response))
			return false, response
		end

		local encodedResponse = table.concat(response)
		local parsedResponse = json.decode(encodedResponse)

		if parsedResponse.status == "succeeded" then

			success, response = request.Get(parsedResponse.resultUrl, additionalHeaders)
			
			encodedResponse = table.concat(response)			
			response = json.decode(encodedResponse)

			local intents = response.assets.intents

			for i, intent in ipairs(intents) do
				intent.id = intent.category
				intent.name = intent.category
				intent.typeId = intent.compositionSetting
				intent.API_VERSION = m_defaultApiVersion
			end
			
			response = json.encode(intents)

			break
		else
			LogInfo("[LUIS_INTENTS:LoadAllAppIntents] Waiting %d seconds.", TIMEOUT_SEC)
			socket.sleep(TIMEOUT_SEC)
		end
	end

	LogInfo("[LUIS_INTENTS:LoadAllAppIntents] response: %s", json.encode(response))

	return true, response
end

function LoadApp (appId, versionId, paginated, vcc) 
	assert(appId)
	assert(versionId)

	local resource_name, subscription_key_1 = GetLanguageStudioAccount(vcc)

	local uri = MakeCompleteURI("APPS", appId .."/:export?api-version=".. m_defaultApiVersion .. "&format=Conversation&stringIndexType=Utf16CodeUnit")
	uri = uri:gsub("resourcename", resource_name)

	LogInfo("[LUIS_APPS40:LoadApp] uri: %s", uri)

	local additionalHeaders = {
		["Ocp-Apim-Subscription-Key"] = subscription_key_1
	}

	local success, response, headers = request.Post(uri, {}, "application/json; charset=UTF-8", additionalHeaders)
	if not success then
		LogError("[LUIS_APPS40:LoadApp] LoadApp Error: '%s'", json.encode(response))
		return false, response
	end

	-- luego obtengo el location
	local operationLocation = headers["operation-location"] 

	LogInfo("[LUIS_INTENTS:LoadApp] operationLocation: %s", operationLocation)

	-- luego consulto N veces ese location

	local success = false
	local response = {}
	local retry = 0
	local TOTAL_RETRIES = 10
	local TIMEOUT_SEC = 3

	while retry < TOTAL_RETRIES do
		retry = retry + 1
		
		success, response = request.Get(operationLocation, additionalHeaders)

		if not success then
			LogError("[LUIS_INTENTS:LoadApp] Error loading all APP COMPOSITE INTENTS on appId '%s' versionId: '%s'. Error: '%s'", appId, versionId, json.encode(response))
			return false, response
		end

		local encodedResponse = table.concat(response)
		local parsedResponse = json.decode(encodedResponse)

		if parsedResponse.status == "succeeded" then

			success, response = request.Get(parsedResponse.resultUrl, additionalHeaders)
			
			encodedResponse = table.concat(response)			
			response = json.decode(encodedResponse)

			break
		else
			LogInfo("[LUIS_INTENTS:LoadApp] Waiting %d seconds.", TIMEOUT_SEC)
			socket.sleep(TIMEOUT_SEC)
		end
	end

	LogInfo("[LUIS_INTENTS:LoadApp] response: %s", json.encode(response))
	--local encodedIntents = table.concat(response)
	return true, response
end

function GetLanguageStudioAccount (vcc) 

	LogInfo(" ------> GetLanguageStudioAccount - vcc: %s", vcc)

	local vccAzureLanguageStudioAccounts, totalVCCAzureLanguageStudioAccounts = LoadVCCAzureLanguageStudioAccount(vcc) 
	local azureLanguageStudio = {} 
	if totalVCCAzureLanguageStudioAccounts > 0 then 
		local firstKey = next(vccAzureLanguageStudioAccounts) 
  		azureLanguageStudio = vccAzureLanguageStudioAccounts[firstKey] 
	else
		azureLanguageStudio = {} 
	end 

    return azureLanguageStudio.resource_name, azureLanguageStudio.subscription_key_1
	
end

function LoadVCCAzureLanguageStudioAccount (vcc, vcc_id) 
	
	if type(vcc) == "table" then
		vcc = vcc_id
	end

	local vccAzureLanguageStudioAccounts = luis_models.VCCs.GetAzureLanguageStudioAccountsMsCognitiveServices(vcc)

	local total = utils.GetRowCount(vccAzureLanguageStudioAccounts)
	return vccAzureLanguageStudioAccounts, total
end

function GetAppIntent (appId, intentId, versionId)
	assert(appId)
	assert(intentId)
	assert(versionId)

	local uri = MakeCompleteURI("APPS", "%s/versions/%s/intents/%s", appId, versionId, intentId)

	local success, response = request.Get(uri)
	if not success then
		LogError("[LUIS_INTENTS:GetAppIntent] Error getting APP INTENTS '%s' on appId '%s' versionId: '%s'. Error: '%s'", intentId, appId, versionId, json.encode(response))
		return false, response
	end

	LogInfo("[LUIS_INTENTS:GetAppIntent] Success get APP INTENTS '%s' on appId '%s' versionId: '%s'.", intentId, appId, versionId)

	local encodedIntent = table.concat(response)
	return true, encodedIntent
end

function CreateAppIntent (info, vcc)
	assert(type(info) == "table")
	assert(info.appId)
	assert(info.versionId)
	assert(vcc)

	local resource_name, subscription_key_1 = GetLanguageStudioAccount(vcc)

	local uri = MakeCompleteURI("APPS", info.appId .."?api-version=".. m_defaultApiVersion)	
	uri = uri:gsub("resourcename", resource_name)

	LogInfo("[LUIS_APPS40:Get] uri: %s", uri)

	local additionalHeaders = {
		["Ocp-Apim-Subscription-Key"] = subscription_key_1
	}

	local success, response = LoadApp(info.appId, info.versionId, info.paginated, vcc)
	if not success then
		LogError("[LUIS_APPS40:Get] Get App '%s' Error: '%s'", info.appId, json.encode(response))
		return false, response
	end

	LogInfo("[LUIS_APPS40:Get] Success Get App %s.", info.appId)
	LogInfo("[LUIS_APPS40:Get] response %s", json.encode(response))

	for _, i in pairs(response.assets.intents) do

		LogInfo("[LUIS_APPS40:CreateAppIntent] i %s", json.encode(i))

		if i.category == info.name then
			return false, {code = 400, reason = "There is already an attempt with the same name.", errType = "inconcert"}
		end

	end

	table.insert(response.assets.intents, {
		category = info.name
	})

	local appId = info.appId .. "/"
	local uri_post = MakeCompleteURI("APPS", appId ..":import?api-version=".. m_defaultApiVersion)
	uri_post = uri_post:gsub("resourcename", resource_name)
	
	local success, response, headers = request.Post(uri_post, json.encode(response), "application/json; charset=UTF-8", additionalHeaders)
	if not success then
		LogError("[LUIS_APPS40:CreateAppIntent] CreateAppIntent Error: '%s'", json.encode(response))
		return false, response
	end

	return true, info.name
end

function RenameAppIntent (info)
	assert(info)
	assert(info.appId)
	assert(info.intentId)
	assert(info.versionId)
	assert(info.name)

	local uri = MakeCompleteURI("APPS", "%s/versions/%s/intents/%s", info.appId, info.versionId, info.intentId)

	local data = {
		---- Requerido
		name = info.name
	}
	local success, response = request.Put(uri, data)
	if not success then
		LogError("[LUIS_INTENTS:RenameAppIntent] Error renaming APP INTENTS '%s' on appId '%s' versionId: '%s'. Response: '%s'", info.intentId, info.appId, info.versionId, json.encode(response))
		return false, response
	end

	LogInfo("[LUIS_INTENTS:RenameAppIntent] Success renamed APP INTENTS '%s' on appId '%s' versionId: '%s'.", info.intentId, info.appId, info.versionId)

	return true, response
end

function DeleteAppIntent (appId, intentId, versionId, vcc)
	
	assert(appId)
	assert(intentId)
	assert(versionId)
	assert(vcc)

	local resource_name, subscription_key_1 = GetLanguageStudioAccount(vcc)

	local uri = MakeCompleteURI("APPS", appId .."?api-version=".. m_defaultApiVersion)	
	uri = uri:gsub("resourcename", resource_name)

	LogInfo("[LUIS_APPS40:Get] uri: %s", uri)

	local additionalHeaders = {
		["Ocp-Apim-Subscription-Key"] = subscription_key_1
	}

	local success, response = LoadApp(appId, versionId, nil, vcc)
	if not success then
		LogError("[LUIS_APPS40:Get] Get App '%s' Error: '%s'", appId, json.encode(response))
		return false, response
	end

	LogInfo("[LUIS_APPS40:Get] Success Get App %s.", appId)
	LogInfo("[LUIS_APPS40:Get] response %s", json.encode(response))

	--local encodedApp = json.decode(response)
	local toRemove = {}

	for index, i in pairs(response.assets.intents) do
		LogInfo("[LUIS_APPS40:DeleteAppIntent] i %s", json.encode(i))
	
		if i.category == intentId then
			table.insert(toRemove, index) -- Agregar el índice a la lista de elementos a eliminar
		end
	end
	
	for i = #toRemove, 1, -1 do
		table.remove(response.assets.intents, toRemove[i])
	end

	local appId = appId .. "/"
	local uri_post = MakeCompleteURI("APPS", appId ..":import?api-version=".. m_defaultApiVersion)
	uri_post = uri_post:gsub("resourcename", resource_name)
	
	local success, response, headers = request.Post(uri_post, json.encode(response), "application/json; charset=UTF-8", additionalHeaders)

	LogError("[LUIS_APPS40:DeleteAppIntent] DeleteAppIntent success: '%s'", success)
	LogError("[LUIS_APPS40:DeleteAppIntent] DeleteAppIntent Error: '%s'", json.encode(response))

	if not success then		
		return false, response
	end

	return true, {code = 200, code = "Success", message = "Operation Successful"}
end

function AddUtteranceAppIntent (info, vcc)
	assert(type(info) == "table")
	assert(info.appId)
	assert(info.versionId)
	assert(info.intentName)
	assert(info.utterance)
	assert(info.language)
	assert(info.dataset)

	LogInfo("AddUtteranceAppIntent: %s", json.encode(info))

	local resource_name, subscription_key_1 = GetLanguageStudioAccount(vcc)

	local uri = MakeCompleteURI("APPS", info.appId .."?api-version=".. m_defaultApiVersion)	
	uri = uri:gsub("resourcename", resource_name)

	LogInfo("[LUIS_APPS40:Get] uri: %s", uri)

	local additionalHeaders = {
		["Ocp-Apim-Subscription-Key"] = subscription_key_1
	}

	local success, response = LoadApp(info.appId, info.versionId, info.paginated, vcc)
	if not success then
		LogError("[LUIS_APPS40:Get] Get App '%s' Error: '%s'", info.appId, json.encode(response))
		return false, response
	end

	LogInfo("[LUIS_APPS40:Get] Success Get App %s.", info.appId)
	LogInfo("[LUIS_APPS40:Get] response %s", json.encode(response))

	local existUlterance = false

	for _, i in pairs(response.assets.utterances) do

		if i.text == info.utterance then
			existUlterance = true

			if i.intent ~= info.intentName then -- es una reasignación de intent
				i.intent = info.intentName
			elseif not info.entityLabels or type(info.entityLabels) ~= "table" then -- es una nueva utterance
				return false, {code = 400, reason = "There is already an attempt with the same name.", errType = "inconcert"}
			else -- es una actualización
				i.dataset = info.dataset
				i.language = info.language
				i.intent = info.intentName
			
				local entities = {}

				if type(info.entityLabels) == "table" 
					and #info.entityLabels > 0 
					and type(info.entityLabels[1].startCharIndex) == "number" 
					and type(info.entityLabels[1].endCharIndex) == "number" then

					for _, j in pairs(info.entityLabels) do
						table.insert(entities, {
							category = j.entityName,
							offset = j.startCharIndex,
							length = ((j.endCharIndex - j.startCharIndex) + 1)
						})
					end

					i.entities = entities

				end

			end			
		end

	end

	if not existUlterance then
		table.insert(response.assets.utterances, {
			text = info.utterance,
			language = info.language,
			intent = info.intentName,
			entities = {},
			dataset = info.dataset -- (Train / Test)
		})
	end
	local appId = info.appId .. "/"
	local uri_post = MakeCompleteURI("APPS", appId ..":import?api-version=".. m_defaultApiVersion)
	uri_post = uri_post:gsub("resourcename", resource_name)
	
	local success, response, headers = request.Post(uri_post, json.encode(response), "application/json; charset=UTF-8", additionalHeaders)
	if not success then
		LogError("[LUIS_APPS40:AddUtteranceAppIntent] AddUtteranceAppIntent Error: '%s'", json.encode(response))
		return false, response
	end

	local response_object = {
		ExampleId = info.utterance,
		UtteranceText = info.utterance
	}

	local response = {}
	table.insert(response, json.encode(response_object))

	return true, response
end

function DeleteAppUtterance (appId, utteranceId, versionId, vcc)
	assert(type(appId) == "string")
	assert(type(utteranceId) == "string")
	assert(type(vcc) == "string")

	local resource_name, subscription_key_1 = GetLanguageStudioAccount(vcc)

	local uri = MakeCompleteURI("APPS", appId .."?api-version=".. m_defaultApiVersion)	
	uri = uri:gsub("resourcename", resource_name)

	LogInfo("[LUIS_APPS40:Get] uri: %s", uri)

	local additionalHeaders = {
		["Ocp-Apim-Subscription-Key"] = subscription_key_1
	}

	local success, response = LoadApp(appId, versionId, nil, vcc)
	if not success then
		LogError("[LUIS_APPS40:Get] Get App '%s' Error: '%s'", appId, json.encode(response))
		return false, response
	end

	LogInfo("[LUIS_APPS40:Get] Success Get App %s.", appId)
	LogInfo("[LUIS_APPS40:Get] response %s", json.encode(response))

	local existUlterance = false

	for index, i in pairs(response.assets.utterances) do
		if i.text == utteranceId then
			table.remove(response.assets.utterances, index)
	        break
		end
	end

	local appId = appId .. "/"
	local uri_post = MakeCompleteURI("APPS", appId ..":import?api-version=".. m_defaultApiVersion)
	uri_post = uri_post:gsub("resourcename", resource_name)
	
	local success, response, headers = request.Post(uri_post, json.encode(response), "application/json; charset=UTF-8", additionalHeaders)
	if not success then
		LogError("[LUIS_APPS40:DeleteAppUtterance] Error: '%s'", json.encode(response))
		return false, response
	end

	local response_object = {
		ExampleId = utteranceId,
		UtteranceText = utteranceId
	}

	local response = {}
	table.insert(response, json.encode(response_object))

	return true, response
end

function findTokenIndex (sentence, offset, length)

	local letters = {}
    local startTokenIndex = nil
    local endTokenIndex = nil
    local currentIndex = 0
    local currentIndexGlobal = 0
	local lastLetter = ""

	for token in sentence:gmatch(".") do
        table.insert(letters, token)
    end

    for i, token in ipairs(letters) do

		if token == " " and lastLetter ~= " " then
            currentIndexGlobal = currentIndexGlobal + 1
        end

		lastLetter = token

        if not startTokenIndex and currentIndex == offset then
            startTokenIndex = currentIndexGlobal
        end

        if currentIndex == (offset + length) then
            if token ~= " " then
                endTokenIndex = currentIndexGlobal
            else
                endTokenIndex = currentIndexGlobal - 1
            end
        end

        currentIndex = currentIndex + 1
    end

    return startTokenIndex, endTokenIndex
end

function LoadUtterancesByAppIntent (appId, versionId, intentId, vcc)
	assert(appId)
	assert(versionId)

	local resource_name, subscription_key_1 = GetLanguageStudioAccount(vcc)

	local uri = MakeCompleteURI("APPS", appId .."/:export?api-version=".. m_defaultApiVersion .. "&format=Conversation&stringIndexType=Utf16CodeUnit")
	uri = uri:gsub("resourcename", resource_name)

	LogInfo("[LUIS_APPS40:LoadUtterancesByAppIntent] uri: %s", uri)

	local additionalHeaders = {
		["Ocp-Apim-Subscription-Key"] = subscription_key_1
	}

	local success, response, headers = request.Post(uri, {}, "application/json; charset=UTF-8", additionalHeaders)
	if not success then
		LogError("[LUIS_APPS40:LoadUtterancesByAppIntent] LoadUtterancesByAppIntent Error: '%s'", json.encode(response))
		return false, response
	end

	-- luego obtengo el location
	local operationLocation = headers["operation-location"] 

	LogInfo("[LUIS_INTENTS:LoadUtterancesByAppIntent] operationLocation: %s", operationLocation)

	-- luego consulto N veces ese location

	local success = false
	local response = {}
	local retry = 0
	local TOTAL_RETRIES = 5
	local TIMEOUT_SEC = 5

	while retry < TOTAL_RETRIES do
		retry = retry + 1
		
		success, response = request.Get(operationLocation, additionalHeaders)

		if not success then
			LogError("[LUIS_INTENTS:LoadUtterancesByAppIntent] Error loading all APP COMPOSITE INTENTS on appId '%s' versionId: '%s'. Error: '%s'", appId, versionId, json.encode(response))
			return false, response
		end

		local encodedResponse = table.concat(response)
		local parsedResponse = json.decode(encodedResponse)

		if parsedResponse.status == "succeeded" then

			success, response = request.Get(parsedResponse.resultUrl, additionalHeaders)

			encodedResponse = table.concat(response)			
			response = json.decode(encodedResponse)

			local utterances = response.assets.utterances
			local utterances_for_intent = {}

			for i, utterance in ipairs(utterances) do

				if utterance.intent == intentId then

					local entityLabels = {}
					local tokenizedText = {}

					for word in utterance.text:gmatch("%S+") do
						table.insert(tokenizedText, word)
					end

					for j, entity in ipairs(utterance.entities) do

						local startTokenIndex, endTokenIndex = findTokenIndex(utterance.text, entity.offset, entity.length)

						table.insert(entityLabels, {
							entityName = entity.category,
							startTokenIndex = startTokenIndex,
							endTokenIndex = endTokenIndex
						})
					end

					table.insert(utterances_for_intent, {
						tokenizedText = tokenizedText,
						id = utterance.text,
						text = utterance.text,
						intentLabel = intentId,
						entityLabels = entityLabels,
						entityPredictions = {},
						intentPredictions = {},
						multiIntentPredictions = nil,
						dataset = utterance.dataset,
						language = utterance.language,
						intent = utterance.intent,
						entities = {}
					})
				end
				
			end
			
			response = json.encode(utterances_for_intent)

			break
		else
			LogInfo("[LUIS_INTENTS:LoadUtterancesByAppIntent] Waiting %d seconds.", TIMEOUT_SEC)
			socket.sleep(TIMEOUT_SEC)
		end
	end

	LogInfo("[LUIS_INTENTS:LoadUtterancesByAppIntent] response: %s", json.encode(response))

	return true, response
end