local validation = {}

local logger = require(script.Parent.Logger)
local utilities = require(script.Parent.Utilities)

function validation:validateCustomDimensions(customDimensions)
	return validation:validateArrayOfStrings(20, 32, false, "custom dimensions", customDimensions)
end

function validation:validateDimension(dimensions, dimension)
	-- allow nil
	if utilities:isStringNullOrEmpty(dimension) then
		return true
	end

	if not utilities:stringArrayContainsString(dimensions, dimension) then
		return false
	end

	return true
end

function validation:validateResourceCurrencies(resourceCurrencies)
	if not validation:validateArrayOfStrings(20, 64, false, "resource currencies", resourceCurrencies) then
		return false
	end

	-- validate each string for regex
	for _, resourceCurrency in pairs(resourceCurrencies) do
		if not string.find(resourceCurrency, "^[A-Za-z]+$") then
			logger:w(
				"resource currencies validation failed: a resource currency can only be A-Z, a-z. String was: "
					.. resourceCurrency
			)
			return false
		end
	end

	return true
end

function validation:validateResourceItemTypes(resourceItemTypes)
	if not validation:validateArrayOfStrings(20, 32, false, "resource item types", resourceItemTypes) then
		return false
	end

	-- validate each string for regex
	for _, resourceItemType in pairs(resourceItemTypes) do
		if not validation:validateEventPartCharacters(resourceItemType) then
			logger:w(
				"resource item types validation failed: a resource item type cannot contain other characters than A-z, 0-9, -_., ()!?. String was: "
					.. resourceItemType
			)
			return false
		end
	end

	return true
end

function validation:validateEventPartCharacters(eventPart)
	if not string.find(eventPart, "^[A-Za-z0-9%s%-_%.%(%)!%?]+$") then
		return false
	end

	return true
end

function validation:validateArrayOfStrings(maxCount, maxStringLength, allowNoValues, logTag, arrayOfStrings)
	local arrayTag = logTag

	if not arrayTag then
		arrayTag = "Array"
	end

	-- use arrayTag to annotate warning log
	if not arrayOfStrings then
		logger:w(arrayTag .. " validation failed: array cannot be nil.")
		return false
	end

	-- check if empty
	if not allowNoValues and #arrayOfStrings == 0 then
		logger:w(arrayTag .. " validation failed: array cannot be empty.")
		return false
	end

	-- check if exceeding max count
	if maxCount > 0 and #arrayOfStrings > maxCount then
		logger:w(
			arrayTag
				.. " validation failed: array cannot exceed "
				.. tostring(maxCount)
				.. " values. It has "
				.. #arrayOfStrings
				.. " values."
		)
		return false
	end

	-- validate each string
	for _, arrayString in ipairs(arrayOfStrings) do
		local stringLength = 0
		if arrayString then
			stringLength = #arrayString
		end

		-- check if empty (not allowed)
		if stringLength == 0 then
			logger:w(arrayTag .. " validation failed: contained an empty string.")
			return false
		end

		-- check if exceeding max length
		if maxStringLength > 0 and stringLength > maxStringLength then
			logger:w(
				arrayTag
					.. " validation failed: a string exceeded max allowed length (which is: "
					.. tostring(maxStringLength)
					.. "). String was: "
					.. arrayString
			)
			return false
		end
	end

	return true
end

function validation:validateBuild(build)
	if not validation:validateShortString(build, false) then
		return false
	end

	return true
end

function validation:validateShortString(shortString, canBeEmpty)
	-- String is allowed to be empty or nil
	if canBeEmpty and utilities:isStringNullOrEmpty(shortString) then
		return true
	end

	if utilities:isStringNullOrEmpty(shortString) or #shortString > 32 then
		return false
	end

	return true
end

function validation:validateKeys(gameKey, secretKey)
	if string.find(gameKey, "^[A-Za-z0-9]+$") and #gameKey == 32 then
		if string.find(secretKey, "^[A-Za-z0-9]+$") and #secretKey == 40 then
			return true
		end
	end

	return false
end

function validation:validateAndCleanInitRequestResponse(initResponse, configsCreated)
	-- make sure we have a valid dict
	if not initResponse then
		logger:w("validateInitRequestResponse failed - no response dictionary.")
		return nil
	end

	local validatedDict = {}

	-- validate server_ts
	local serverTsNumber = initResponse["server_ts"] or -1
	if serverTsNumber > 0 then
		validatedDict["server_ts"] = serverTsNumber
	end

	if configsCreated then
		validatedDict["configs"] = initResponse["configs"] or {}
		validatedDict["ab_id"] = initResponse["ab_id"] or ""
		validatedDict["ab_variant_id"] = initResponse["ab_variant_id"] or ""
	end

	return validatedDict
end

function validation:validateClientTs(clientTs)
	if clientTs < 1000000000 or clientTs > 9999999999 then
		return false
	end

	return true
end

function validation:validateCurrency(currency)
	if utilities:isStringNullOrEmpty(currency) then
		return false
	end

	if string.find(currency, "^[A-Z]+$") and #currency == 3 then
		return true
	end

	return false
end

function validation:validateEventPartLength(eventPart, allowNull)
	if allowNull and utilities:isStringNullOrEmpty(eventPart) then
		return true
	end

	if utilities:isStringNullOrEmpty(eventPart) then
		return false
	end

	if #eventPart == 0 or #eventPart > 64 then
		return false
	end
	return true
end

function validation:validateBusinessEvent(currency, amount, cartType, itemType, itemId)
	-- validate currency
	if not validation:validateCurrency(currency) then
		logger:w(
			"Validation fail - business event - currency: Cannot be (null) and need to be A-Z, 3 characters and in the standard at openexchangerates.org. Failed currency: "
				.. currency
		)
		return false
	end

	if amount < 0 then
		logger:w("Validation fail - business event - amount: Cannot be less then 0. Failed amount: " .. amount)
		return false
	end

	-- validate cartType
	if not validation:validateShortString(cartType, true) then
		logger:w("Validation fail - business event - cartType. Cannot be above 32 length. String: " .. cartType)
		return false
	end

	-- validate itemType length
	if not validation:validateEventPartLength(itemType, false) then
		logger:w(
			"Validation fail - business event - itemType: Cannot be (null), empty or above 64 characters. String: "
				.. itemType
		)
		return false
	end

	-- validate itemType chars
	if not validation:validateEventPartCharacters(itemType) then
		logger:w(
			"Validation fail - business event - itemType: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: "
				.. itemType
		)
		return false
	end

	-- validate itemId
	if not validation:validateEventPartLength(itemId, false) then
		logger:w(
			"Validation fail - business event - itemId. Cannot be (null), empty or above 64 characters. String: "
				.. itemId
		)
		return false
	end

	if not validation:validateEventPartCharacters(itemId) then
		logger:w(
			"Validation fail - business event - itemId: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: "
				.. itemId
		)
		return false
	end

	return true
end

function validation:validateResourceEvent(
	flowTypeValues,
	flowType,
	currency,
	amount,
	itemType,
	itemId,
	currencies,
	itemTypes
)
	if flowType ~= flowTypeValues.Source and flowType ~= flowTypeValues.Sink then
		logger:w("Validation fail - resource event - flowType: Invalid flow type " .. tostring(flowType))
		return false
	end

	if utilities:isStringNullOrEmpty(currency) then
		logger:w("Validation fail - resource event - currency: Cannot be (null)")
		return false
	end

	if not utilities:stringArrayContainsString(currencies, currency) then
		logger:w(
			"Validation fail - resource event - currency: Not found in list of pre-defined available resource currencies. String: "
				.. currency
		)
		return false
	end

	if not (amount > 0) then
		logger:w(
			"Validation fail - resource event - amount: Float amount cannot be 0 or negative. Value: "
				.. tostring(amount)
		)
		return false
	end

	if utilities:isStringNullOrEmpty(itemType) then
		logger:w("Validation fail - resource event - itemType: Cannot be (null)")
		return false
	end

	if not validation:validateEventPartLength(itemType, false) then
		logger:w(
			"Validation fail - resource event - itemType: Cannot be (null), empty or above 64 characters. String: "
				.. itemType
		)
		return false
	end

	if not validation:validateEventPartCharacters(itemType) then
		logger:w(
			"Validation fail - resource event - itemType: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: "
				.. itemType
		)
		return false
	end

	if not utilities:stringArrayContainsString(itemTypes, itemType) then
		logger:w(
			"Validation fail - resource event - itemType: Not found in list of pre-defined available resource itemTypes. String: "
				.. itemType
		)
		return false
	end

	if not validation:validateEventPartLength(itemId, false) then
		logger:w(
			"Validation fail - resource event - itemId: Cannot be (null), empty or above 64 characters. String: "
				.. itemId
		)
		return false
	end

	if not validation:validateEventPartCharacters(itemId) then
		logger:w(
			"Validation fail - resource event - itemId: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: "
				.. itemId
		)
		return false
	end

	return true
end

function validation:validateProgressionEvent(
	progressionStatusValues,
	progressionStatus,
	progression01,
	progression02,
	progression03
)
	if
		progressionStatus ~= progressionStatusValues.Start
		and progressionStatus ~= progressionStatusValues.Complete
		and progressionStatus ~= progressionStatusValues.Fail
	then
		logger:w("Validation fail - progression event: Invalid progression status " .. tostring(progressionStatus))
		return false
	end

	-- Make sure progressions are defined as either 01, 01+02 or 01+02+03
	if
		not utilities:isStringNullOrEmpty(progression03)
		and not (not utilities:isStringNullOrEmpty(progression02) or utilities:isStringNullOrEmpty(progression01))
	then
		logger:w(
			"Validation fail - progression event: 03 found but 01+02 are invalid. Progression must be set as either 01, 01+02 or 01+02+03."
		)
		return false
	elseif not utilities:isStringNullOrEmpty(progression02) and utilities:isStringNullOrEmpty(progression01) then
		logger:w(
			"Validation fail - progression event: 02 found but not 01. Progression must be set as either 01, 01+02 or 01+02+03"
		)
		return false
	elseif utilities:isStringNullOrEmpty(progression01) then
		logger:w(
			"Validation fail - progression event: progression01 not valid. Progressions must be set as either 01, 01+02 or 01+02+03"
		)
		return false
	end

	-- progression01 (required)
	if not validation:validateEventPartLength(progression01, false) then
		logger:w(
			"Validation fail - progression event - progression01: Cannot be (null), empty or above 64 characters. String: "
				.. progression01
		)
		return false
	end

	if not validation:validateEventPartCharacters(progression01) then
		logger:w(
			"Validation fail - progression event - progression01: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: "
				.. progression01
		)
		return false
	end

	-- progression02
	if not utilities:isStringNullOrEmpty(progression02) then
		if not validation:validateEventPartLength(progression02, false) then
			logger:w(
				"Validation fail - progression event - progression02: Cannot be empty or above 64 characters. String: "
					.. progression02
			)
			return false
		end

		if not validation:validateEventPartCharacters(progression02) then
			logger:w(
				"Validation fail - progression event - progression02: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: "
					.. progression02
			)
			return false
		end
	end

	-- progression03
	if not utilities:isStringNullOrEmpty(progression03) then
		if not validation:validateEventPartLength(progression03, false) then
			logger:w(
				"Validation fail - progression event - progression03: Cannot be empty or above 64 characters. String: "
					.. progression03
			)
			return false
		end

		if not validation:validateEventPartCharacters(progression03) then
			logger:w(
				"Validation fail - progression event - progression03: Cannot contain other characters than A-z, 0-9, -_., ()!?. String: "
					.. progression03
			)
			return false
		end
	end

	return true
end

function validation:validateEventIdLength(eventId)
	if utilities:isStringNullOrEmpty(eventId) then
		return false
	end

	local count = 0
	for s in string.gmatch(eventId, "([^:]+)") do
		count = count + 1
		if count > 5 or #s > 64 then
			return false
		end
	end

	return true
end

function validation:validateEventIdCharacters(eventId)
	if utilities:isStringNullOrEmpty(eventId) then
		return false
	end

	local count = 0
	for s in string.gmatch(eventId, "([^:]+)") do
		count = count + 1
		if count > 5 or not string.find(s, "^[A-Za-z0-9%s%-_%.%(%)!%?]+$") then
			return false
		end
	end

	return true
end

function validation:validateDesignEvent(eventId)
	if not validation:validateEventIdLength(eventId) then
		logger:w(
			"Validation fail - design event - eventId: Cannot be (null) or empty. Only 5 event parts allowed seperated by :. Each part need to be 32 characters or less. String: "
				.. eventId
		)
		return false
	end

	if not validation:validateEventIdCharacters(eventId) then
		logger:w(
			"Validation fail - design event - eventId: Non valid characters. Only allowed A-z, 0-9, -_., ()!?. String: "
				.. eventId
		)
		return false
	end

	-- value: allow 0, negative and nil (not required)
	return true
end

function validation:validateLongString(longString, canBeEmpty)
	-- String is allowed to be empty
	if canBeEmpty and utilities:isStringNullOrEmpty(longString) then
		return true
	end

	if utilities:isStringNullOrEmpty(longString) or #longString > 8192 then
		return false
	end

	return true
end

function validation:validateErrorEvent(severityValues, severity, message)
	if
		severity ~= severityValues.debug
		and severity ~= severityValues.info
		and severity ~= severityValues.warning
		and severity ~= severityValues.error
		and severity ~= severityValues.critical
	then
		logger:w("Validation fail - error event - severity: Severity was unsupported value " .. tostring(severity))
		return false
	end

	if not validation:validateLongString(message, true) then
		logger:w("Validation fail - error event - message: Message cannot be above 8192 characters.")
		return false
	end

	return true
end

return validation
