extends Node

# Talks to the FastAPI matchmaking service (see /matchmaking-api). No client
# library — plain HTTPRequest per call, since traffic here is low-frequency
# (one join, then a poll every ~1.5s) and each request gets its own
# short-lived HTTPRequest node so calls never collide with each other.

signal search_started
signal match_found(server_ip: String, server_port: int)
signal search_failed(reason: String)

const POLL_INTERVAL := 1.5

var base_url: String = "http://127.0.0.1:8100"

var _ticket_id: String = ""
var _poll_timer: Timer
var _searching: bool = false


func _ready() -> void:
	for arg in OS.get_cmdline_user_args():
		if arg.begins_with("--matchmaking-api="):
			base_url = arg.substr("--matchmaking-api=".length())

	_poll_timer = Timer.new()
	_poll_timer.wait_time = POLL_INTERVAL
	_poll_timer.one_shot = false
	_poll_timer.timeout.connect(_poll_status)
	add_child(_poll_timer)


func start_matchmaking(callsign: String, mode: String) -> void:
	if _searching:
		return
	_searching = true
	search_started.emit()

	var result := await _request("POST", "/matchmaking/queue/join", {
		"callsign": callsign,
		"mode": mode,
	})

	if not _searching:
		return  # cancelled while the join request was in flight

	if result.get("code", 0) != 200:
		_fail("Couldn't reach matchmaking service")
		return

	_ticket_id = result.body.ticket_id
	_poll_timer.start()


# Returns {"ok": bool, "servers": Array} — one dict per row from GET /servers
# (ip, port, mode, status, player_count, max_players, last_heartbeat). "ok"
# is false if the API couldn't be reached at all, distinct from a reachable
# API returning an empty list.
func list_servers() -> Dictionary:
	var result := await _request("GET", "/servers", null)
	if result.get("code", 0) != 200:
		return {"ok": false, "servers": []}
	return {"ok": true, "servers": result.body}


# Called by NetworkManager once on server boot and then repeatedly as a
# heartbeat — same endpoint both times, an unrecognized (ip, port, mode)
# creates a row, a recognized one refreshes it. Returns true on success;
# NetworkManager doesn't hard-fail on a missed heartbeat (the API's own
# stale-server sweep just marks it offline until the next one lands), so
# callers can fire-and-forget this.
func register_server(ip: String, port: int, mode: String, player_count: int, max_players: int) -> bool:
	var result := await _request("POST", "/servers/register", {
		"ip": ip,
		"port": port,
		"mode": mode,
		"player_count": player_count,
		"max_players": max_players,
	})
	return result.get("code", 0) == 200


func cancel_matchmaking() -> void:
	if not _searching:
		return
	_searching = false
	_poll_timer.stop()
	if _ticket_id != "":
		_request("POST", "/matchmaking/queue/leave?ticket_id=" + _ticket_id, null)
	_ticket_id = ""


func _poll_status() -> void:
	if _ticket_id == "":
		return
	var result := await _request("GET", "/matchmaking/queue/status/" + _ticket_id, null)

	if not _searching:
		return  # cancelled while the poll was in flight

	if result.get("code", 0) != 200:
		_fail("Lost contact with matchmaking service")
		return

	var status: String = result.body.status
	if status == "matched":
		_poll_timer.stop()
		_searching = false
		var ip: String = result.body.server_ip
		var port: int = result.body.server_port
		_ticket_id = ""
		match_found.emit(ip, port)
	elif status == "cancelled":
		_poll_timer.stop()
		_searching = false
		_ticket_id = ""
		_fail("Queue ticket was cancelled")
	# else "queued": keep polling


func _fail(reason: String) -> void:
	_poll_timer.stop()
	_searching = false
	_ticket_id = ""
	search_failed.emit(reason)


# Returns {"code": int, "body": Dictionary} — "code" is 0 if the request
# itself couldn't even be sent (e.g. malformed URL), not an HTTP status.
func _request(method: String, path: String, body) -> Dictionary:
	var http := HTTPRequest.new()
	add_child(http)

	var headers := PackedStringArray()
	var body_str := ""
	if body != null:
		headers.append("Content-Type: application/json")
		body_str = JSON.stringify(body)

	var http_method := HTTPClient.METHOD_GET if method == "GET" else HTTPClient.METHOD_POST
	var err := http.request(base_url + path, headers, http_method, body_str)
	if err != OK:
		http.queue_free()
		return {"code": 0, "body": null}

	var response = await http.request_completed
	http.queue_free()

	var response_code: int = response[1]
	var response_body: PackedByteArray = response[3]
	var text := response_body.get_string_from_utf8()
	var parsed = JSON.parse_string(text) if text != "" else null
	return {"code": response_code, "body": parsed}
