local validation = require(script.Parent.Validation)
local logger = require(script.Parent.Logger)
local http_api = require(script.Parent.HttpApi)
local store = require(script.Parent.Store)
local events = require(script.Parent.Events)
local HTTP = game:GetService("HttpService")

local state = {
	_availableCustomDimensions01 = {},
	_availableCustomDimensions02 = {},
	_availableCustomDimensions03 = {},
	_availableGamepasses = {},
	_enableEventSubmission = true,
	Initialized = false,
	ReportErrors = true,
	UseCustomUserId = false,
	AutomaticSendBusinessEvents = true,
	ConfigsHash = "",
}

local GameAnalyticsRemoteConfigs

local function getClientTsAdjusted(playerId)
	local PlayerData = store:GetPlayerDataFromCache(playerId)
	if not PlayerData then
		return os.time()
	end

	local clientTs = os.time()
	local clientTsAdjustedInteger = clientTs + PlayerData.ClientServerTimeOffset
	if validation:validateClientTs(clientTsAdjustedInteger) then
		return clientTsAdjustedInteger
	else
		return clientTs
	end
end

local function populateConfigurations(player)
	local PlayerData = store:GetPlayerDataFromCache(player.UserId)
	local sdkConfig = PlayerData.SdkConfig

	if sdkConfig["configs"] then
		local configurations = sdkConfig["configs"]

		for _, configuration in pairs(configurations) do
			if configuration then
				local key = configuration["key"] or ""
				local start_ts = configuration["start_ts"] or 0
				local end_ts = configuration["end_ts"] or math.huge
				local client_ts_adjusted = getClientTsAdjusted(player.UserId)

				if
					#key > 0
					and configuration["value"]
					and client_ts_adjusted > start_ts
					and client_ts_adjusted < end_ts
				then
					PlayerData.Configurations[key] = configuration["value"]
					logger:d(
						"configuration added: key=" .. configuration["key"] .. ", value=" .. configuration["value"]
					)
				end
			end
		end
	end

	logger:i("Remote configs populated")

	PlayerData.RemoteConfigsIsReady = true
	GameAnalyticsRemoteConfigs = GameAnalyticsRemoteConfigs
		or game:GetService("ReplicatedStorage"):WaitForChild("GameAnalyticsRemoteConfigs")
	GameAnalyticsRemoteConfigs:FireClient(player, PlayerData.Configurations)
end

function state:sessionIsStarted(playerId)
	local PlayerData = store:GetPlayerDataFromCache(playerId)
	if not PlayerData then
		return false
	end

	return PlayerData.SessionStart ~= 0
end

function state:isEnabled(playerId)
	local PlayerData = store:GetPlayerDataFromCache(playerId)
	if not PlayerData then
		return false
	elseif not PlayerData.InitAuthorized then
		return false
	else
		return true
	end
end

function state:validateAndFixCurrentDimensions(playerId)
	local PlayerData = store:GetPlayerDataFromCache(playerId)

	-- validate that there are no current dimension01 not in list
	if not validation:validateDimension(self._availableCustomDimensions01, PlayerData.CurrentCustomDimension01) then
		logger:d(
			"Invalid dimension01 found in variable. Setting to nil. Invalid dimension: "
				.. PlayerData.CurrentCustomDimension01
		)
	end

	-- validate that there are no current dimension02 not in list
	if not validation:validateDimension(self._availableCustomDimensions02, PlayerData.CurrentCustomDimension02) then
		logger:d(
			"Invalid dimension02 found in variable. Setting to nil. Invalid dimension: "
				.. PlayerData.CurrentCustomDimension02
		)
	end

	-- validate that there are no current dimension03 not in list
	if not validation:validateDimension(self._availableCustomDimensions03, PlayerData.CurrentCustomDimension03) then
		logger:d(
			"Invalid dimension03 found in variable. Setting to nil. Invalid dimension: "
				.. PlayerData.CurrentCustomDimension03
		)
	end
end

function state:setAvailableCustomDimensions01(availableCustomDimensions)
	if not validation:validateCustomDimensions(availableCustomDimensions) then
		return
	end

	self._availableCustomDimensions01 = availableCustomDimensions
	logger:i("Set available custom01 dimension values: (" .. table.concat(availableCustomDimensions, ", ") .. ")")
end

function state:setAvailableCustomDimensions02(availableCustomDimensions)
	if not validation:validateCustomDimensions(availableCustomDimensions) then
		return
	end

	self._availableCustomDimensions02 = availableCustomDimensions
	logger:i("Set available custom02 dimension values: (" .. table.concat(availableCustomDimensions, ", ") .. ")")
end

function state:setAvailableCustomDimensions03(availableCustomDimensions)
	if not validation:validateCustomDimensions(availableCustomDimensions) then
		return
	end

	self._availableCustomDimensions03 = availableCustomDimensions
	logger:i("Set available custom03 dimension values: (" .. table.concat(availableCustomDimensions, ", ") .. ")")
end

function state:setAvailableGamepasses(availableGamepasses)
	self._availableGamepasses = availableGamepasses
	logger:i("Set available game passes: (" .. table.concat(availableGamepasses, ", ") .. ")")
end

function state:setEventSubmission(flag)
	self._enableEventSubmission = flag
end

function state:isEventSubmissionEnabled()
	return self._enableEventSubmission
end

function state:setCustomDimension01(playerId, dimension)
	local PlayerData = store:GetPlayerDataFromCache(playerId)
	PlayerData.CurrentCustomDimension01 = dimension
end

function state:setCustomDimension02(playerId, dimension)
	local PlayerData = store:GetPlayerDataFromCache(playerId)
	PlayerData.CurrentCustomDimension02 = dimension
end

function state:setCustomDimension03(playerId, dimension)
	local PlayerData = store:GetPlayerDataFromCache(playerId)
	PlayerData.CurrentCustomDimension03 = dimension
end

function state:startNewSession(player, teleportData, customFields)
	if state:isEventSubmissionEnabled() and teleportData == nil then
		logger:i("Starting a new session.")
	end

	local PlayerData = store:GetPlayerDataFromCache(player.UserId)

	-- make sure the current custom dimensions are valid
	state:validateAndFixCurrentDimensions(player.UserId)

	local initResult = http_api:initRequest(events.GameKey, events.SecretKey, events.Build, PlayerData, player.UserId)
	local statusCode = initResult.statusCode
	local responseBody = initResult.body

	if
		(statusCode == http_api.EGAHTTPApiResponse.Ok or statusCode == http_api.EGAHTTPApiResponse.Created)
		and responseBody
	then
		-- set the time offset - how many seconds the local time is different from servertime
		local timeOffsetSeconds = 0
		local serverTs = responseBody["server_ts"] or -1
		if serverTs > 0 then
			local clientTs = os.time()
			timeOffsetSeconds = serverTs - clientTs
		end

		responseBody["time_offset"] = timeOffsetSeconds

		if not (statusCode == http_api.EGAHTTPApiResponse.Created) then
			local sdkConfig = PlayerData.SdkConfig

			if sdkConfig["configs"] then
				responseBody["configs"] = sdkConfig["configs"]
			end

			if sdkConfig["ab_id"] then
				responseBody["ab_id"] = sdkConfig["ab_id"]
			end

			if sdkConfig["ab_variant_id"] then
				responseBody["ab_variant_id"] = sdkConfig["ab_variant_id"]
			end
		end

		PlayerData.SdkConfig = responseBody
		PlayerData.InitAuthorized = true
	elseif statusCode == http_api.EGAHTTPApiResponse.Unauthorized then
		logger:w("Initialize SDK failed - Unauthorized")
		PlayerData.InitAuthorized = false
	else
		-- log the status if no connection
		if
			statusCode == http_api.EGAHTTPApiResponse.NoResponse
			or statusCode == http_api.EGAHTTPApiResponse.RequestTimeout
		then
			logger:i("Init call (session start) failed - no response. Could be offline or timeout.")
		elseif
			statusCode == http_api.EGAHTTPApiResponse.BadResponse
			or statusCode == http_api.EGAHTTPApiResponse.JsonEncodeFailed
			or statusCode == http_api.EGAHTTPApiResponse.JsonDecodeFailed
		then
			logger:i("Init call (session start) failed - bad response. Could be bad response from proxy or GA servers.")
		elseif
			statusCode == http_api.EGAHTTPApiResponse.BadRequest
			or statusCode == http_api.EGAHTTPApiResponse.UnknownResponseCode
		then
			logger:i("Init call (session start) failed - bad request or unknown response.")
		end

		PlayerData.InitAuthorized = true
	end

	-- set offset in state (memory) from current config (config could be from cache etc.)
	PlayerData.ClientServerTimeOffset = PlayerData.SdkConfig["time_offset"] or 0
	PlayerData.ConfigsHash = PlayerData.SdkConfig["configs_hash"] or ""
	PlayerData.AbId = PlayerData.SdkConfig["ab_id"] or ""
	PlayerData.AbVariantId = PlayerData.SdkConfig["ab_variant_id"] or ""

	-- populate configurations
	populateConfigurations(player)

	if not state:isEnabled(player.UserId) then
		logger:w("Could not start session: SDK is disabled.")
		return
	end

	if teleportData then
		PlayerData.SessionID = teleportData.SessionID
		PlayerData.SessionStart = teleportData.SessionStart
	else
		PlayerData.SessionID = string.lower(HTTP:GenerateGUID(false))
		PlayerData.SessionStart = getClientTsAdjusted(player.UserId)
	end

	if state:isEventSubmissionEnabled() then
		events:addSessionStartEvent(player.UserId, teleportData, customFields)
	end
end

function state:endSession(playerId, customFields)
	if state.Initialized and state:isEventSubmissionEnabled() then
		logger:i("Ending session.")
		if state:isEnabled(playerId) and state:sessionIsStarted(playerId) then
			events:addSessionEndEvent(playerId, customFields)
			store.PlayerCache[playerId] = nil
		end
	end
end

function state:getRemoteConfigsStringValue(playerId, key, defaultValue)
	local PlayerData = store:GetPlayerDataFromCache(playerId)
	return PlayerData.Configurations[key] or defaultValue
end

function state:isRemoteConfigsReady(playerId)
	local PlayerData = store:GetPlayerDataFromCache(playerId)
	return PlayerData.RemoteConfigsIsReady
end

function state:getRemoteConfigsContentAsString(playerId)
	local PlayerData = store:GetPlayerDataFromCache(playerId)
	return HTTP:JSONEncode(PlayerData.Configurations)
end

return state
