## Server Main — Headless dedicated server entry point.
##
## Runs as `server_main.tscn` which is the project's main scene.
## In headless mode (--headless), this is the only running scene.
##
## On ready:
##   1. Load config (env overrides)
##   2. Load the first map from the config's map rotation
##   3. Start ENet server on the configured port
##   4. Create GameServer and start simulation
##   5. Spawn players on the map using spawn markers
##   6. Broadcast spawn via RPC so clients create visual player nodes
##   7. Log player join/leave and replicate position
##   8. Hitscan weapon with lag compensation runs in GameServer

extends Node

# ---------------------------------------------------------------------------
# Signals
# ---------------------------------------------------------------------------
signal player_spawned(peer_id: int)
signal player_despawned(peer_id: int)

# ---------------------------------------------------------------------------
# Exports
# ---------------------------------------------------------------------------
@export var player_scene: PackedScene = preload("res://scenes/player.tscn")

# ---------------------------------------------------------------------------
# State
# ---------------------------------------------------------------------------
var players: Dictionary = {}  # peer_id → Node (player instance)
var spawn_points_a: Array[Vector3] = []  # Team A spawns
var spawn_points_b: Array[Vector3] = []  # Team B spawns

# Game server (drives simulation)
var _game_server: Node = null

# ---------------------------------------------------------------------------
# Lifecycle
# ---------------------------------------------------------------------------
func _ready() -> void:
	# Config driven — ServerConfig singleton loaded at autoload time.
	# Allow port override via env var for backwards compatibility
	# (container/VPS deployments that set SERVER_PORT).
	var effective_port: int = ServerConfig.port
	if OS.has_environment("SERVER_PORT"):
		effective_port = int(OS.get_environment("SERVER_PORT"))
		print("[ServerMain] Port overridden by SERVER_PORT env: %d" % effective_port)

	# Create GameServer (drives the 128Hz simulation + hit detection)
	_game_server = preload("res://server/scripts/game_server.gd").new()
	add_child(_game_server)
	# GameServer registers SimulationServer as a singleton on _ready
	_game_server.start_simulation()

	# Instance the map from the config's map rotation
	_load_map()

	# Start the ENet server
	var err: Error = NetworkManager.start_server(effective_port)
	if err != OK:
		push_error("[ServerMain] Failed to start server: %s" % error_string(err))
		get_tree().quit(1)
		return

	# Connect signals
	NetworkManager.player_connected.connect(_on_player_connected)
	NetworkManager.player_disconnected.connect(_on_player_disconnected)

	print("[ServerMain] Tactical Shooter server ready")
	print("[ServerMain]   Port:       %d" % effective_port)
	print("[ServerMain]   Name:       \"%s\"" % ServerConfig.server_name)
	print("[ServerMain]   Tick rate:  %d Hz" % ServerConfig.tick_rate)
	print("[ServerMain]   Maps:       %s" % str(ServerConfig.map_list))
	print("[ServerMain]   Headless:   %s" % (DisplayServer.get_name() == &"headless"))
	print("[ServerMain]   Spawn pts:  Team A=%d, Team B=%d" % [spawn_points_a.size(), spawn_points_b.size()])
	var lag_comp_ms: float = 64.0 * 1000.0 / ServerConfig.tick_rate
	print("[ServerMain]   Lag comp:   Enabled (64-tick history = %.1fms)" % lag_comp_ms)

func _exit_tree() -> void:
	NetworkManager.stop()

# ---------------------------------------------------------------------------
# Map loading
# ---------------------------------------------------------------------------
func _load_map() -> void:
	# Load the first map from the config's rotation list
	var maps_list: Array[String] = ServerConfig.map_list
	if maps_list.is_empty():
		push_warning("[ServerMain] Map rotation is empty — running without map geometry")
		return

	var first_map_path: String = maps_list[0]
	var full_path: String = "res://scenes/map/%s.tscn" % first_map_path
	
	if not ResourceLoader.exists(full_path):
		push_error("[ServerMain] Map scene not found: %s" % full_path)
		return
	
	var map_scene_res: PackedScene = load(full_path)
	var map_instance: Node = map_scene_res.instantiate()
	map_instance.name = "Map_%s" % first_map_path
	add_child(map_instance, true)
	
	print("[ServerMain] Loaded map: %s" % first_map_path)
	
	# Collect spawn markers from the map scene
	_collect_spawn_points(map_instance)

## Walk the map scene looking for Marker3D nodes named "SpawnA*" and "SpawnB*".
func _collect_spawn_points(map_root: Node) -> void:
	_collect_spawn_points_recursive(map_root)
	
	# Fallback: only after full recursion, if still empty
	if spawn_points_a.is_empty() and spawn_points_b.is_empty():
		print("[ServerMain] No spawn markers found — using default positions")
		spawn_points_a.append(Vector3(-16, 0, -4))
		spawn_points_a.append(Vector3(-16, 0, 4))
		spawn_points_b.append(Vector3(16, 0, -4))
		spawn_points_b.append(Vector3(16, 0, 4))

func _collect_spawn_points_recursive(node: Node) -> void:
	if node is Marker3D:
		var name_lower: String = node.name.to_lower()
		if name_lower.begins_with("spawna") or name_lower.begins_with("spawn_a"):
			spawn_points_a.append(node.position)
		elif name_lower.begins_with("spawnb") or name_lower.begins_with("spawn_b"):
			spawn_points_b.append(node.position)
	
	for child in node.get_children():
		_collect_spawn_points_recursive(child)

# ---------------------------------------------------------------------------
# Player management
# ---------------------------------------------------------------------------
func _on_player_connected(peer_id: int) -> void:
	print("[ServerMain] Player joined: %d. Total: %d" % [peer_id, multiplayer.get_peers().size() + 1])
	_spawn_player(peer_id)

func _on_player_disconnected(peer_id: int) -> void:
	print("[ServerMain] Player left: %d" % peer_id)
	_despawn_player(peer_id)

func _spawn_player(peer_id: int) -> void:
	# Create player node on the server
	var player: Node
	if player_scene:
		player = player_scene.instantiate()
	else:
		player = Node3D.new()
		player.set_script(preload("res://scripts/network/player.gd"))

	player.name = "Player_%d" % peer_id
	player.set_multiplayer_authority(peer_id)

	# Assign spawn position — alternate between Team A and Team B spawns
	var is_team_a: bool = (peer_id % 2) == 0
	var pool: Array[Vector3] = spawn_points_a if is_team_a else spawn_points_b
	var spawn_pos: Vector3 = pool[peer_id % pool.size()] if pool.size() > 0 else Vector3.ZERO
	spawn_pos.y = 0.0
	player.position = spawn_pos

	add_child(player, true)
	players[peer_id] = player
	player_spawned.emit(peer_id)

	# Register with GameServer to get a simulation entity
	if _game_server != null and _game_server.has_method(&"spawn_player_entity"):
		var entity_id: int = _game_server.spawn_player_entity(peer_id, spawn_pos)
		if entity_id >= 0:
			# Store entity_id on player node for reference
			player.set_meta(&"entity_id", entity_id)
			# Register with lag compensation and damage processor
			# (player is a Node3D, which is a valid argument for register_player_node)
			if _game_server.has_method(&"register_player_node"):
				_game_server.register_player_node(entity_id, player)
			print("[ServerMain] Player %d assigned entity %d" % [peer_id, entity_id])

	# Broadcast spawn to all clients so they create a visual player node
	NetworkManager.broadcast_spawn_player.rpc(peer_id, spawn_pos, is_team_a)

	print("[ServerMain] Spawned player %d at (%.1f, %.1f) team=%s" % [peer_id, spawn_pos.x, spawn_pos.z, "A" if is_team_a else "B"])

func _despawn_player(peer_id: int) -> void:
	if peer_id in players:
		var player: Node = players[peer_id]

		# Despawn entity from GameServer
		if _game_server != null and player.has_meta(&"entity_id"):
			var entity_id: int = player.get_meta(&"entity_id")
			# Unregister from lag compensation and damage processor first
			if _game_server.has_method(&"unregister_player_node"):
				_game_server.unregister_player_node(entity_id)
			_game_server.despawn_player_entity(entity_id)

		player.queue_free()
		players.erase(peer_id)
		player_despawned.emit(peer_id)

		# Broadcast despawn to all clients
		NetworkManager.broadcast_despawn_player.rpc(peer_id)

		print("[ServerMain] Despawned player %d" % peer_id)
	else:
		# Still broadcast even if server doesn't have the player
		NetworkManager.broadcast_despawn_player.rpc(peer_id)

# ---------------------------------------------------------------------------
# Server tick (128 Hz)
# ---------------------------------------------------------------------------
func _physics_process(delta: float) -> void:
	if not NetworkManager.is_server:
		return
	
	# Future: authoritative physics tick, state broadcast, etc.
	pass

# ---------------------------------------------------------------------------
# Client position replication (receives from FPS character clients)
# ---------------------------------------------------------------------------
@rpc("unreliable", "any_peer")
func _send_position(pos: Vector3) -> void:
	# Called by remote clients to update their position on the server.
	# Broadcast to all other peers so they see the player move.
	var peer_id: int = multiplayer.get_remote_sender_id()
	if peer_id in players:
		players[peer_id].position = pos
		# Re-broadcast to all other peers
		_replicate_position.rpc(pos, peer_id)

@rpc("unreliable", "authority")
func _replicate_position(pos: Vector3, exclude_peer: int) -> void:
	# All clients receive this and update the specific player's position.
	# We need to find which remote player this belongs to by excluding our own.
	# For now, the non-authority clients will need to filter — handled in client_main.gd
	pass
