extends Node

signal peer_connected(id: int)
signal peer_disconnected(id: int)
signal connected_to_server()
signal connection_failed()

var peers: Dictionary = {}
var my_id: int = 0

var players: Dictionary = {}  # peer_id (int) → {name: String, role: String}

signal player_joined(peer_id: int, player_name: String, role: String)
signal player_left(peer_id: int)
signal player_list_synced()
signal game_started()
signal roll_received(roller_peer_id: int, player_name: String, d20_result: int, modifier: int, total: int)


func start_server(port: int, max_clients: int) -> void:
	var peer := ENetMultiplayerPeer.new()
	var err := peer.create_server(port, max_clients)
	if err != OK:
		push_error("[Server] Port %d konnte nicht gebunden werden: %s" % [port, error_string(err)])
		return
	multiplayer.peer_connected.connect(_on_peer_connected)
	multiplayer.peer_disconnected.connect(_on_peer_disconnected)
	multiplayer.multiplayer_peer = peer
	print("[Server] Gestartet auf Port %d" % port)


func join_server(ip: String, port: int) -> void:
	var peer := ENetMultiplayerPeer.new()
	var err := peer.create_client(ip, port)
	if err != OK:
		push_error("[Client] Verbindung zu %s:%d fehlgeschlagen: %s" % [ip, port, error_string(err)])
		return
	multiplayer.connected_to_server.connect(_on_connected_to_server)
	multiplayer.connection_failed.connect(_on_connection_failed)
	multiplayer.multiplayer_peer = peer


func _on_peer_connected(id: int) -> void:
	peers[id] = {}
	peer_connected.emit(id)
	print("[Server] Peer verbunden: %d" % id)
	welcome.rpc_id(id, id)


func _on_peer_disconnected(id: int) -> void:
	peers.erase(id)
	players.erase(id)
	peer_disconnected.emit(id)
	player_left.emit(id)
	_broadcast_player_left.rpc(id)
	print("[Server] Peer getrennt: %d" % id)


func _on_connected_to_server() -> void:
	my_id = multiplayer.get_unique_id()
	connected_to_server.emit()
	print("[Client] Verbunden. Meine peer_id: %d" % my_id)


func _on_connection_failed() -> void:
	connection_failed.emit()
	push_error("[Client] Verbindung fehlgeschlagen")


@rpc("authority", "call_remote", "reliable")
func welcome(peer_id: int) -> void:
	# call_remote: wird NUR auf dem Ziel-Client ausgeführt, nicht auf dem Server.
	print("[Client] Willkommen, meine peer_id ist: %d" % peer_id)


@rpc("any_peer", "call_remote", "reliable")
func register(player_name: String, role: String) -> void:
	if not multiplayer.is_server():
		return
	var sender_id := multiplayer.get_remote_sender_id()
	if player_name.strip_edges().is_empty():
		kick.rpc_id(sender_id, "Spielername darf nicht leer sein.")
		return
	if role == "dm":
		for p in players.values():
			if p.role == "dm":
				kick.rpc_id(sender_id, "Es ist bereits ein DM verbunden.")
				return
	# Erst bestehende Spielerliste an neuen Client senden (noch ohne den neuen Spieler)
	sync_players.rpc_id(sender_id, players)
	# Dann neuen Spieler an alle broadcasten (inkl. Server selbst durch call_local)
	_broadcast_player_joined.rpc(sender_id, player_name, role)


@rpc("authority", "call_local", "reliable")
func _broadcast_player_joined(peer_id: int, player_name: String, role: String) -> void:
	players[peer_id] = {name = player_name, role = role}
	player_joined.emit(peer_id, player_name, role)


@rpc("authority", "call_remote", "reliable")
func sync_players(player_list: Dictionary) -> void:
	# Nur Dict befüllen — kein Signal per Spieler emittieren.
	# Lobby-UI rebuilt die Liste einmalig via player_list_synced.
	players = player_list
	player_list_synced.emit()


@rpc("authority", "call_remote", "reliable")
func kick(reason: String) -> void:
	push_error("[Client] Verbindung abgelehnt: %s" % reason)
	multiplayer.multiplayer_peer.close.call_deferred()


@rpc("any_peer", "call_remote", "reliable")
func request_start_game() -> void:
	if not multiplayer.is_server():
		return
	var requester_id := multiplayer.get_remote_sender_id()
	if players.get(requester_id, {}).get("role", "") != "dm":
		return
	var assignments := _build_room_assignments()
	start_game.rpc(assignments)


@rpc("authority", "call_local", "reliable")
func start_game(room_assignments: Dictionary) -> void:
	if my_id in room_assignments:
		SceneManager.pending_room_index = room_assignments[my_id]
	game_started.emit()


func _build_room_assignments() -> Dictionary:
	var result := {}
	var player_peers: Array = players.keys().filter(
		func(id: int) -> bool: return players[id].role == "player"
	)
	for i in player_peers.size():
		result[player_peers[i]] = i % 8  # 8 Gästezimmer (Index 0–7)
	return result


# call_remote: Server hat lokal bereits gelöscht; nur Clients führen aus
@rpc("authority", "call_remote", "reliable")
func _broadcast_player_left(peer_id: int) -> void:
	players.erase(peer_id)
	player_left.emit(peer_id)


@rpc("any_peer", "call_remote", "reliable")
func request_set_overlay(target_peer_id: int, overlay_name: String) -> void:
	if not multiplayer.is_server():
		return
	var requester_id := multiplayer.get_remote_sender_id()
	if players.get(requester_id, {}).get("role", "") != "dm":
		return
	set_overlay.rpc_id(target_peer_id, overlay_name)


@rpc("authority", "call_remote", "reliable")
func set_overlay(overlay_name: String) -> void:
	GameState.set_overlay_local(my_id, overlay_name)


@rpc("any_peer", "call_remote", "unreliable")
func sync_player_position(player_id: int, position: Vector3, rotation: Vector3) -> void:
	GameState.update_player_transform(player_id, position, rotation)


# Client calls this on server only (rpc_id(1, ...))
@rpc("any_peer", "call_remote", "reliable")
func broadcast_roll(roller_peer_id: int, d20_result: int, modifier: int) -> void:
	if not multiplayer.is_server(): return
	_relay_roll.rpc(roller_peer_id, d20_result, modifier)


# Server broadcasts to all (including itself via call_local)
@rpc("authority", "call_local", "reliable")
func _relay_roll(roller_peer_id: int, d20_result: int, modifier: int) -> void:
	var player_name: String = players.get(roller_peer_id, {}).get("name", "???")
	var total := d20_result + modifier
	roll_received.emit(roller_peer_id, player_name, d20_result, modifier, total)
