@tool
extends AbstractReservationRepo
class_name ReservationRepoHTTP

const BASE_URL = "http://89.223.87.146:5004"
const RESERVATION_ENDPOINT = "/reservation/"
const HEALTH_ENDPOINT = "/health/"

signal request_failed(error_message: String)

var _jwt_token: String = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MDA0MDQzNywianRpIjoiOTM4NTUyMjMtMjhiNC00OWVhLWI3ZjUtZmYxMTg4YzI1Mjg2IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3RfdXNlciIsIm5iZiI6MTc0MDA0MDQzNywiY3NyZiI6ImMwYzI2MTU0LTdkNjItNGYyZi04ZjBhLWI0MjA0ODJlMmEzZCIsImV4cCI6MTc0MDA0MTMzN30.esPRTXNxrtziuOc2dUsc9XqbedErjcyvPlL3aUZIaaA"
var _timed_out := false
var _http_client := HTTPTalkpalClient.new(BASE_URL)

func _init():
	add_child(_http_client)
	_http_client.request_failed.connect(_on_request_failed)

func _ready():
	if await _backend_is_accessible():
		connected.emit()
	else:
		not_connected.emit()

func _backend_is_accessible():
	var result = await _http_client.request(HEALTH_ENDPOINT, HTTPClient.METHOD_GET)

	if not result:
		push_error("Server initialization failed")
		return false
	if result.has("error"):
		push_error("Server initialization failed: " + result.error)
		return false

	return true

#region Public Methods
func create_reservation(dto: CreateReservationDTO) -> void:
	_http_client.request(RESERVATION_ENDPOINT, HTTPClient.METHOD_POST, _dto_to_json(dto))

func cancel_reservation(reservation_id: String) -> void:
	_http_client.request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_DELETE)

func change_reservation(reservation_id: String, dto: UpdateReservationDTO) -> void:
	_http_client.request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_PUT, _dto_to_json(dto))

func get_reservation(reservation_id: String) -> ReservationEntity:
	var result = await _http_client.request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_GET)
	return _parse_reservation_entity(result) if result is Dictionary else null

func list_reservations(filters: Dictionary = {}) -> Array:
	var query = "?"
	for key in filters:
		query += "%s=%s&" % [key, filters[key]]
	query = query.rstrip("&") if filters else ""

	var endpoint = RESERVATION_ENDPOINT + query
	var result = await _http_client.request(endpoint, HTTPClient.METHOD_GET)
	var reservations = _parse_reservation_list(result) if result is Array else []

	_set_current_reservation_id_from_reservations(reservations)
	return reservations

func _set_current_reservation_id_from_reservations(reservations):
	var time = Time.get_time_dict_from_system()
	var current_time_in_minutes = time.hour*60 + time.minute

	for reservation in reservations:
		var start_time_in_minutes = reservation.start_time.hours*60 + reservation.start_time.minutes
		var finish_time_in_minutes = reservation.finish_time.hours*60 + reservation.finish_time.minutes

		if current_time_in_minutes >= start_time_in_minutes \
		and current_time_in_minutes < finish_time_in_minutes:
			_current_reservation = reservation
			return

	_current_reservation = null

func set_selected_reservation_id(value):
	_selected_reservation_id = value

func get_selected_reservation_id():
	return _selected_reservation_id

func set_jwt_token(token: String):
	_jwt_token = token
#endregion

#region Private Methods
func _get_headers() -> PackedStringArray:
	return PackedStringArray([
		"Content-Type: application/json",
		"Authorization: Bearer %s" % _jwt_token
	])

func _dto_to_json(dto) -> String:
	var dict = {}
	match typeof(dto):
		TYPE_OBJECT when dto is CreateReservationDTO:
			dict = {
				"title": dto.title,
				"description": dto.description,
				"room_id": dto.room_id,
				"date": dto.date,
				"start_time": dto.start_time,
				"finish_time": dto.finish_time,
				"color": dto.color
			}
		TYPE_OBJECT when dto is UpdateReservationDTO:
			if dto.title: dict["title"] = dto.title
			if dto.description: dict["description"] = dto.description
			if dto.room_id: dict["room_id"] = dto.room_id
			if dto.date: dict["date"] = dto.date
			if dto.start_time: dict["start_time"] = dto.start_time
			if dto.finish_time: dict["finish_time"] = dto.finish_time
			if dto.color: dict["color"] = dto.color
	return JSON.stringify(dict)

func _time_dto_to_dict(time_dto: TimeDTO) -> Dictionary:
	return {"hours": time_dto.hours, "minutes": time_dto.minutes}

func _make_request(endpoint: String, method: int, body: String = "") -> Variant:
	var request = HTTPRequest.new()
	
	add_child(request)

	var url = BASE_URL + endpoint
	var error = request.request(url, _get_headers(), method, body)
	if error != OK:
		request.queue_free()
		emit_signal("request_failed", "Request creation failed: %d" % error)
		return null

	var response = await _wait_for_request_or_timeout(request)

	request.queue_free()

	var result = response[0]
	var response_code = response[1]
	var response_body = response[3]
	
	if result != HTTPRequest.RESULT_SUCCESS:
		emit_signal("request_failed", "HTTP request failed: %d" % result)
		return null
	
	var json = JSON.new()
	var parse_error = json.parse(response_body.get_string_from_utf8())
	
	if parse_error != OK:
		emit_signal("request_failed", "JSON parse error: %d" % parse_error)
		return null
	
	var response_data = json.get_data()
	
	if response_code >= 400:
		emit_signal("request_failed", response_data.get("error", "Unknown error"))
		return null
	
	return response_data

func _wait_for_request_or_timeout(request: HTTPRequest) -> Array:
	var timer = Timer.new()
	add_child(timer)

	timer.wait_time = 5.0
	timer.one_shot = true

	timer.timeout.connect(func():
		_timed_out = true
		if request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
			request.cancel_request()
	)

	timer.start()

	while true:
		if _timed_out:
			_timed_out = false
			timer.queue_free()
			return [HTTPRequest.RESULT_REQUEST_FAILED, 408, [], PackedByteArray()]
		if request.get_http_client_status() == HTTPClient.STATUS_REQUESTING:
			break
		await get_tree().process_frame

	timer.queue_free()
	return await request.request_completed

func _parse_reservation_entity(data: Dictionary) -> ReservationEntity:
	var entity = ReservationEntity.new()
	entity.id = data.get("id", "")
	entity.title = data.get("title", "")
	entity.description = data.get("description", "")
	entity.room_id = data.get("room_id", "")
	entity.creator = data.get("creator", "")
	entity.date = data.get("date", "")
	entity.color = data.get("color", 0)
	entity.start_time = data.get("start_time", {})
	entity.finish_time = data.get("finish_time", {})
	
	return entity

func _parse_reservation_list(data: Array) -> Array[ReservationEntity]:
	var entities: Array[ReservationEntity] = []
	for item in data:
		entities.append(_parse_reservation_entity(item))

	entities.sort_custom(_compare_reservations_by_start_time)
	return entities

func _compare_reservations_by_start_time(a: ReservationEntity, b: ReservationEntity) -> int:
	var time_a = a.start_time.get("hours", 0) * 60 + a.start_time.get("minutes", 0)
	var time_b = b.start_time.get("hours", 0) * 60 + b.start_time.get("minutes", 0)
	return time_a < time_b
#endregion

func _on_request_failed(error_message: String):
	request_failed.emit(error_message)
