local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local GameConfig = require(ReplicatedStorage.Shared.GameConfig)
local StateView = require(ReplicatedStorage.Shared.StateView)

local Logger = require(script.Parent.Services.Logger)
local PlayerDataService = require(script.Parent.Services.PlayerDataService)
local ZooService = require(script.Parent.Services.ZooService)
local ConveyorService = require(script.Parent.Services.ConveyorService)
local IncomeService = require(script.Parent.Services.IncomeService)
local HatchingService = require(script.Parent.Services.HatchingService)
local EventService = require(script.Parent.Services.EventService)
local TradeService = require(script.Parent.Services.TradeService)
local RateLimiterService = require(script.Parent.Services.RateLimiterService)

local rateLimiter = RateLimiterService.new()

local function nowUnix()
	return os.time()
end

local function ensureRemotes()
	local remotes = ReplicatedStorage:FindFirstChild("Remotes")
	if remotes == nil then
		remotes = Instance.new("Folder")
		remotes.Name = "Remotes"
		remotes.Parent = ReplicatedStorage
	end

	local function ensureRemote(className, name)
		local existing = remotes:FindFirstChild(name)
		if existing ~= nil then
			return existing
		end
		local remote = Instance.new(className)
		remote.Name = name
		remote.Parent = remotes
		return remote
	end

	return {
		RequestBuyEgg = ensureRemote("RemoteFunction", "RequestBuyEgg"),
		PlaceEgg = ensureRemote("RemoteEvent", "PlaceEgg"),
		RequestUpgrade = ensureRemote("RemoteFunction", "RequestUpgrade"),
		SellAnimal = ensureRemote("RemoteFunction", "SellAnimal"),
		RequestState = ensureRemote("RemoteFunction", "RequestState"),
		StateSync = ensureRemote("RemoteEvent", "StateSync"),
	}
end

local Remotes = ensureRemotes()

local function syncState(player)
	local state = PlayerDataService:Get(player)
	if state == nil then
		return
	end
	Remotes.StateSync:FireClient(player, StateView.ToClientState(state))
end

local function rejectForRateLimit(player, action)
	Logger.Warn("ServerMain", "Rate limit reached", { userId = player.UserId, action = action })
	return false, "RateLimited"
end

Remotes.RequestBuyEgg.OnServerInvoke = function(player, eggType)
	local allowed = rateLimiter:Allow(player.UserId, "BuyEgg", GameConfig.RateLimits.BuyEggPer10s, 10)
	if allowed == false then
		return rejectForRateLimit(player, "BuyEgg")
	end

	local state = PlayerDataService:Get(player)
	if state == nil then
		return false, "NoState"
	end

	local ok, tokenOrReason = ZooService:TryBuyEgg(state, eggType)
	if ok == false then
		return false, tokenOrReason
	end

	syncState(player)
	return true, tokenOrReason
end

Remotes.PlaceEgg.OnServerEvent:Connect(function(player, tokenId, x, y)
	local allowed = rateLimiter:Allow(player.UserId, "PlaceEgg", GameConfig.RateLimits.PlaceEggPer10s, 10)
	if allowed == false then
		Remotes.StateSync:FireClient(player, { error = "RateLimited" })
		return
	end

	local state = PlayerDataService:Get(player)
	if state == nil then
		Remotes.StateSync:FireClient(player, { error = "NoState" })
		return
	end

	local ok, reason = ZooService:TryPlaceEgg(state, tokenId, x, y, nowUnix())
	if ok == false then
		Remotes.StateSync:FireClient(player, { error = reason })
		return
	end

	syncState(player)
end)

Remotes.RequestUpgrade.OnServerInvoke = function(player, upgradeType)
	local allowed = rateLimiter:Allow(player.UserId, "Upgrade", GameConfig.RateLimits.UpgradePer10s, 10)
	if allowed == false then
		return rejectForRateLimit(player, "Upgrade")
	end

	local state = PlayerDataService:Get(player)
	if state == nil then
		return false, "NoState"
	end

	local ok, reason
	if upgradeType == "Conveyor" then
		ok, reason = ConveyorService:TryUpgrade(state)
	elseif upgradeType == "Plot" then
		ok, reason = ZooService:TryUpgradePlot(state)
	else
		return false, "UnknownUpgrade"
	end

	if ok == false then
		return false, reason
	end

	syncState(player)
	return true, nil
end

Remotes.SellAnimal.OnServerInvoke = function(player, x, y)
	local allowed = rateLimiter:Allow(player.UserId, "Sell", GameConfig.RateLimits.SellPer10s, 10)
	if allowed == false then
		return rejectForRateLimit(player, "Sell")
	end

	local state = PlayerDataService:Get(player)
	if state == nil then
		return false, "NoState"
	end

	local ok, valueOrReason = TradeService:SellAnimalToNpc(state, x, y)
	if ok == false then
		return false, valueOrReason
	end

	syncState(player)
	return true, valueOrReason
end

Remotes.RequestState.OnServerInvoke = function(player)
	local state = PlayerDataService:Get(player)
	if state == nil then
		return false, "NoState"
	end
	return true, StateView.ToClientState(state)
end

Players.PlayerAdded:Connect(function(player)
	local ok, reason = PlayerDataService:Load(player)
	if ok == false then
		player:Kick(string.format("Unable to load profile: %s", reason))
		return
	end

	local state = PlayerDataService:Get(player)
	ConveyorService:RefreshOffers(state, nowUnix())
	syncState(player)
end)

local accumulator = 0
local syncAccumulator = 0

RunService.Heartbeat:Connect(function(dt)
	accumulator += dt
	syncAccumulator += dt
	if accumulator < GameConfig.IncomeTickSeconds then
		return
	end

	local tickDt = accumulator
	accumulator = 0
	local now = nowUnix()
	local eventModifiers = EventService:GetActiveModifiers(now)

	for _, player in ipairs(Players:GetPlayers()) do
		local state = PlayerDataService:Get(player)
		if state ~= nil then
			if ConveyorService:ShouldRefresh(state, now) then
				ConveyorService:RefreshOffers(state, now)
			end

			IncomeService:Tick(state, tickDt, eventModifiers)
			HatchingService:Run(state, now, eventModifiers)
		end
	end

	if syncAccumulator >= GameConfig.StateSyncSeconds then
		syncAccumulator = 0
		for _, player in ipairs(Players:GetPlayers()) do
			syncState(player)
		end
	end
end)

