--=============================================================================
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 addDays(date, amount)
	return date:add(0, 0, amount) -- year, month, day
end

-- returns the day after `date`
local function nextDay(date)
	return addDays(date, 1)
end

--
-- `dateStr` must be something like "2010-01-01 [00:00:00]", the time part is ignored
local function parseDate(dateStr)
	if dateStr == "" then
		return nil
	end

	local y,m,d = string.match(dateStr, "^(%d%d%d%d)-(%d%d)-(%d%d)")
	if not (y and m and d) then
		return nil, ("String '%s' could not be interpreted as a date."):format(dateStr)
	end
	return LuaOrm.Date({ year = y, month = m, day = d, hour = 0 })
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,
		propertyId	= input.property,
		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

local function buildBatchesRange(batches, lastHandle)
	return tonumber(lastHandle or 0) + 1, #batches
end

local function emptyValue(_T)
	return _T and _T("N/A") or "N/A"
end

--
-- fixed indicators, campaign, vcc, date, etc
local function getValueForFakeIndicator(connection, params)
	local propertyId = params.indicator.propertyId
	if propertyId == "batch" then
		return params.batch.name
	end
	if propertyId == "campaign" then
		return params.batch.campaign
	end
	if propertyId == "startDate" then
		return (params.batch.startDate and params.batch.startDate:toString("%Y-%m-%d")) or emptyValue(params._T)
	end
	if propertyId == "endDate" then
		return (params.batch.endDate and params.batch.endDate:toString("%Y-%m-%d")) or emptyValue(params._T)
	end
	return "Unknown: ".. tostring(indicatorName)
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

--[[
-- Example indicator:
--  [default@Historical] get InteractionCounters['ReceivedMailMessages:kamp1@wilderkrieger:ByDay']
--   => (counter=2014-04-25 00:00:00.000, value=1)
--  [default@Historical] get InteractionCounters['ReceivedMailMessages:MAIL:kamp1@wilderkrieger:ByDay']
--   => (counter=2014-04-25 00:00:00.000, value=1)
--]]
local function getPlainIndicatorValue(connection, params)
	local indicator = params.indicator
	return params.batchIndicators[indicator.indicatorId] or 0
end

local function getCompositeValue(indicator, getValueFn)
	local function buildIndicator(input)
		return parseIndicator(input, "indicatorId")
	end
	local function getValue(input)
		if type(input) == "number" then
			return input
		end
		return getValueFn(buildIndicator(input))
	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(connection, params)
	if params.indicator.indicatorId then
		return getPlainIndicatorValue(connection, params)
	end

	if params.indicator.composite then
		return getCompositeValue(params.indicator, function(subIndicator)
			return getIndicatorValue(connection, _union(params, {
				indicator = subIndicator
			}))
		end)
	end

	return getValueForFakeIndicator(connection, params)
end

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

	local lastHandle      = queryTable.lastHandle
	local batches         = queryTable.batches
	local interactionType = queryTable.interactionType
	local indicators      = parseIndicators(queryTable.projectFields)
	local summary		  = queryTable.summary or nil
	local oeClient		  = queryTable.oeClient
	assert(type(batches) == "table", "Invalid batches argument")

	local scheduleId       = queryTable.queryFields and queryTable.queryFields.scheduleId or nil
	local isSentToDatabase = utils and scheduleId and  scheduler.is_send_type(utils, scheduleId, "database")
	local rawData          = isSentToDatabase
--rawData = true
	LogDebug("[REPORTER.OUTBOUNDDIALERBATCHS_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 startIndex, endIndex, err = buildBatchesRange(batches, lastHandle)
	if not (startIndex and endIndex) then
		return nil, err
	end

	local function batchesIterator(results, index, computeBatch)
		if index > endIndex then
			return results, index
		end

		-- not happy about it but inner function returns an array
		_append(results, computeBatch(index))

		-- may return more than asked, but not less
		if #results >= resultSize then
			return results, index
		end

		-- tail call with next batch
		return batchesIterator(results, index + 1, computeBatch)
	end

	local summaryMetadata = nil
	local summaryHeaders = nil
	local results, lastIndex = batchesIterator({}, startIndex, function(index)
		-- returns the row
		local batch = batches[index]
		batch.startDate = parseDate(batch.startDate)
		batch.endDate = parseDate(batch.endDate)
		local batchId = batch.name .. ":" .. batch.processId
		local status, batchIndicators = oeClient.batches:GetTotalIndicators(batch.processId, batchId)
		batchIndicators = status and batchIndicators or {}

		local result = _map(indicators, function(indicator)
			local res = getIndicatorValue(connection, {
				indicator       = indicator,
				interactionType = interactionType,
				batch           = batch,
				batchId         = batchId,
				rawData         = rawData,
				batchIndicators = batchIndicators,
				_T 				= _T
			})
			if type(res) == "number" then
				if not rawData then
					if indicator.type == "time" then
						return reporter.slices_helper.formatTime(res)
					end
					if indicator.type == "percentage" then
						return string.format("%.2f %%", res)
					end
					if math.floor(res) ~= res then
						return string.format("%.2f", res)
					end
				end
				return tostring(res)
			end
			return res
		end)

		--process summary
		if summary then
			local nowStartDate = LuaOrm.Date():toString("%Y-%m-%d") .. " 00:00:00"
			local nowEndDate = LuaOrm.Date():toString("%Y-%m-%d") .. " 23:59:59"
			if batch.startDate then
				summary.filter.DateFrom = batch.startDate:toString("%Y-%m-%d") .. " 00:00:00"
			else
				summary.filter.DateFrom = nowStartDate
			end
			if batch.endDate then
				summary.filter.DateTo = batch.endDate:toString("%Y-%m-%d") .. " 23:59:59"
				if summary.filter.DateTo > nowEndDate then
					summary.filter.DateTo = nowEndDate
				end
			else
				summary.filter.DateTo = nowEndDate
			end
			summary.filter.DialerCampaign = batch.campaign
			summary.filter.BatchId = batch.name
			local summaryResult = QuerySummary(summary, utils, _T, idsToNames or {})
			assert(summaryResult, "Error running summary part for batch " .. batchId)
			local row = summaryResult.length > 0 and summaryResult.rows[1] or nil
			local headersDiff = #summaryResult.headers - #summary.indicators
			for index, _ in pairs(summary.indicators) do
				table.insert(result, row and row[index + headersDiff] or "")
			end
			if not summaryMetadata and row then
				summaryMetadata = {}
				summaryHeaders = {}
				for index, _ in pairs(summary.indicators) do
					table.insert(summaryMetadata, summaryResult.columnsMetadata[index + headersDiff])
					table.insert(summaryHeaders, summaryResult.headers[index + headersDiff])
				end
			end
		end

		return result
	end)

	local headers = _map(indicators, function(indicator)
		return indicator.label
	end)
	local columnsMetadata = _map(indicators, function(indicator)
		return indicator
	end)

	if summary then
		if summaryHeaders then
			for _, label in pairs(summaryHeaders) do
				table.insert(headers, label)
			end
		else
			for _, indicator in pairs(summary.indicators) do
				table.insert(headers, _T(summary.columnDetails[indicator].columnName or indicator))
			end
		end
		if summaryMetadata then
			for _, metadata in pairs(summaryMetadata) do
				table.insert(columnsMetadata, metadata)
			end
		end
	end

	return {
		headers			= headers,
		rows			= results,
		length			= #results,
		columnsMetadata	= columnsMetadata,
	}, lastIndex

end

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