--=============================================================================
local LuaOrm = {
	Date = require "LuaOrm.Date"
}
local pl = {
	tablex = require "pl.tablex"
}
local reporter = {
	slices_helper = require "reporter.slices_helper"
}

local scheduler = require "reporter.scheduler"
--=============================================================================

--
-- Having the function before the table does not seem natural
-- so we create a wrapper around it
local function _map(tbl, fn)
	return pl.tablex.map(fn, tbl)
end

-- just a shortcut
local _foreach = pl.tablex.foreach

-- inserts `v` at the end of `tbl` and returns `tbl`
local function _append(tbl, v)
	table.insert(tbl, v)
	return tbl
end

-- merge `tbl1` and `tbl2`, `tbl2` may overwrite values in `tbl1`
local function _union(tbl1, tbl2)
	return pl.tablex.merge(tbl1, tbl2, true)
end

local function parseIndicator(input, defaultField)
	if type(input) == "string" then
		return { [defaultField] = input }
	end
	return {
		name        = input.name or input.dimension,
		label       = input.label or input.dimensionData and input.dimensionData.label,
		description = input.description or input.dimensionData and input.dimensionData.description,
		indicatorId = input.indicator,
		composite   = input.composite,
		type        = input.type or input.dimensionData and input.dimensionData.type,
		domain      = input.domain or input.dimensionData and input.dimensionData.domain,
	}
end

--
-- Converts `projectedFields` into something we can actually use
local function parseIndicators(projectedFields)
	return _map(projectedFields, function(elem)
		return parseIndicator(elem, "name")
		-- TODO: Some crazy stuff (only if required)
	end)
end

--
-- helper
local function getFromCassandra(connection, key, pred)
	local columns = connection:GetSliceRange(key, "InteractionCounters", "", pred)
	for _, column in ipairs(columns) do
		return column.value_as_number -- there's only one value
	end
end

local function getFormatFunction (timezone, _T, rawData, utils)
	local trueLabel = _T("Yes")
	local falseLabel = _T("No")
	local utcTimezone = {offset = 0} -- Default to UTC
	timezone = rawData and utcTimezone or timezone

	local formatDateTypes = {
		Date     = "%Y-%m-%d",
		DateTime = "%Y-%m-%d %H:%M:%S"
	}

	return function(indicator, value)
		local formatDate = formatDateTypes[indicator.type]

		if formatDate then
			return reporter.slices_helper.ConvertUTCDateToTimezoneDate(
				tonumber(value), timezone, formatDate, utils, true).stringDate
		elseif indicator.type == "Time" then
			value = tonumber(value) or 0
			if rawData then
				return value
			end
			return reporter.slices_helper.formatTime(value)
		elseif indicator.type == "boolean" then
			value = tostring(value)
			if rawData then
				return value == "1" and true or false
			end
			return value == "1" and trueLabel or falseLabel
		elseif indicator.type == "enum" then
			if rawData then
				return value
			end
			return indicator.domain[value] or value
		end

		if not value or value == "" then
			return "N/A"
		end
		return value
	end
end

local function getPlainIndicatorValue(indicator, data, formatFunction)
	local value = data[indicator.indicatorId]
	return formatFunction(indicator, value)
end

local function getCompositeValue(indicator, getValueFn, formatFunction)
	local function buildIndicator(input)
		return parseIndicator(input, "indicatorId")
	end
	local function getValue(input)
		if type(input) == "number" then
			return input
		end
		local value = getValueFn(buildIndicator(input))
		return tonumber(value) or 0
	end

	local conf = indicator.composite

	if conf.operation == "percentage" then
		local total = getValue(conf.total)
		local value = getValue(conf.value)
		return total == 0 and 100 or value * 100 / total
	end

	if conf.operation == "difference" or conf.operation == "sum" then
		local operand1 = getValue(conf.operand1)
		local operand2 = getValue(conf.operand2)
		return operand1 + (conf.operation == "sum" and operand2 or -operand2)
	end

	if conf.operation == "average" then
		local count = getValue(conf.count)
		local sum = getValue(conf.sum)
		return count == 0 and 0 or sum / count
	end

	if conf.operation == "division" then
		local numerator = getValue(conf.numerator)
		local denominator = getValue(conf.denominator)
		return numerator / math.max(1, denominator)
	end

	return 0
end

local function getIndicatorValue(indicator, data, formatFunction)
	if indicator.composite then
		return getCompositeValue(params.indicator, function(subIndicator)
			return getIndicatorValue(subIndicator, data, formatFunction)
		end)
	end
	return getPlainIndicatorValue(indicator, data, formatFunction)
end

local function getContactsFromCassandra(connection, campaigns, interactionType, batch, resultSize, lastContact, vcc, utils)
	local result = {}

	local campaign = campaigns[1]
	if not campaign then
		LogInfo("[REPORTER.DIALERCONTACTS_QUERY:getContactsFromCassandra] Filter without Campaign")
		return result
	end

	campaign = utils.AppendVcc(utils.GetId(campaign), vcc)

	local batchKey = batch .. ":" .. interactionType .. ":" .. campaign

	--LogDebug("[REPORTER.DIALERCONTACTS_QUERY:getContactsFromCassandra] batchKey: '%s'", batchKey)

	local rows = connection:GetSuperRangeSlices("DialerLastContactResultDetail",
		"",
		batchKey,
		batchKey,
		{
			column_names = {},
			slice_range = {
				start = lastContact,
				finish = "",
				reversed = false,
				count = resultSize
			}
		},
		1
	)
	for key, contacts in rows:Iterator() do
		if key == batchKey then
			return contacts
		end
	end

	return result
end

--
--
local function query (connection, queryTable, utils, _T, idsToNames)
	--LogDebug("[REPORTER.DIALERCONTACTS_QUERY:query] Executing Query Reports. queryTable: '%s'", json.encode(queryTable))

	local queryFields       = queryTable.queryFields or {}
	local campaigns         = queryFields.Campaign
	local lastContact		= queryTable.lastHandle or ""
	local skipFirst			= lastContact ~= ""
	local batch				= queryTable.batch
	local contactId			= queryTable.contactId
	local interactionType	= queryTable.interactionType
	local timezone			= queryTable.timezone
	local indicators		= parseIndicators(queryTable.projectFields)

	assert(campaigns, "Invalid campaigns argument")
	assert(batch, "Invalid batch argument")
	assert(interactionType, "Invalid interaction Type argument")
	
	local scheduleId       = queryFields.scheduleId or nil
	local isSentToDatabase = utils and scheduleId and  scheduler.is_send_type(utils, scheduleId, "database")
	local rawData          = isSentToDatabase
--rawData = true
	LogDebug("[REPORTER.DIALERCONTACTS_QUERY:query] scheduleId: %s, isSentToDatabase %s, rawData %s", scheduleId, isSentToDatabase, rawData)
	if isSentToDatabase then
		-- Si es un RUN de tipo database, tiene que consultar datos infinitos, por esto agregamos un numero
		queryTable.resultSize = 99999999
	end
	local resultSize = queryTable.resultSize or 40
	assert(type(resultSize) == "number" and resultSize > 0, "Invalid resultSize argument")

	local formatFunction = getFormatFunction(timezone, _T, rawData, utils)

	if skipFirst then
		resultSize = resultSize + 1
	end

	if contactId and contactId ~= "" then
		lastContact = contactId
		resultSize = 1
	else
		contactId = false
	end

	local headers = _map(indicators, function(indicator)
		return indicator.label
	end)
	--get contacts
	local cassandraContacts = getContactsFromCassandra(connection, campaigns, interactionType, batch, resultSize, lastContact, queryFields.VCC, utils)
	--process result
	local results = {}
	for _, cassandraContact in pairs(cassandraContacts) do
		if skipFirst then
			skipFirst = false
		else
			local contact = {}
			for colid, column in pairs(cassandraContact.columns) do
				contact[column.name] = column.value
			end

			if not contactId or contactId == contact.id then
				local result = _map(indicators, function(indicator)
					return getIndicatorValue(indicator, contact, formatFunction)
				end)
				table.insert(results, result)
				lastContact = contact.id
			end
		end
	end

	return {
		headers			= headers,
		columnsMetadata	= indicators,
		rows			= results,
		length			= #results,
	}, lastContact
end

--=============================================================================
return {
	query = query
}
--=============================================================================
