extends Node
## HTTPClient Service
##
## Singleton service for all HTTP communication with the Flask backend.
## Handles authentication, JSON parsing, error handling, and provides
## a convenient interface for making API requests.
##
## Usage:
##   HTTPClient.http_get("/api/v1/characters", _on_characters_loaded)
##   HTTPClient.http_post("/api/v1/auth/login", {"email": "...", "password": "..."}, _on_login_complete)

# API Configuration
const API_TIMEOUT := 30.0  # Seconds

# HTTP Methods
enum Method {
	GET,
	POST,
	PUT,
	DELETE,
	PATCH
}

# Internal state
var _auth_token: String = ""
var _session_cookie: String = ""  # Session cookie for authentication
#var _request_queue: Array[Dictionary] = []
var _active_requests: Dictionary = {}  # request_id -> HTTPRequest node


func _ready() -> void:
	print("[HTTPClient] Service initialized. API Base URL: %s" % Settings.get_api_url())


## Make a GET request
##
## @param endpoint: API endpoint (e.g., "/api/v1/characters")
## @param callback: Function to call with APIResponse when complete
## @param error_callback: Optional function to call on error
func http_get(endpoint: String, callback: Callable, error_callback: Callable = Callable()) -> void:
	_make_request(Method.GET, endpoint, {}, callback, error_callback)


## Make a POST request
##
## @param endpoint: API endpoint
## @param data: Dictionary to send as JSON body
## @param callback: Function to call with APIResponse when complete
## @param error_callback: Optional function to call on error
func http_post(endpoint: String, data: Dictionary, callback: Callable, error_callback: Callable = Callable()) -> void:
	_make_request(Method.POST, endpoint, data, callback, error_callback)


## Make a PUT request
##
## @param endpoint: API endpoint
## @param data: Dictionary to send as JSON body
## @param callback: Function to call with APIResponse when complete
## @param error_callback: Optional function to call on error
func http_put(endpoint: String, data: Dictionary, callback: Callable, error_callback: Callable = Callable()) -> void:
	_make_request(Method.PUT, endpoint, data, callback, error_callback)


## Make a DELETE request
##
## @param endpoint: API endpoint
## @param callback: Function to call with APIResponse when complete
## @param error_callback: Optional function to call on error
func http_delete(endpoint: String, callback: Callable, error_callback: Callable = Callable()) -> void:
	_make_request(Method.DELETE, endpoint, {}, callback, error_callback)


## Make a PATCH request
##
## @param endpoint: API endpoint
## @param data: Dictionary to send as JSON body
## @param callback: Function to call with APIResponse when complete
## @param error_callback: Optional function to call on error
func http_patch(endpoint: String, data: Dictionary, callback: Callable, error_callback: Callable = Callable()) -> void:
	_make_request(Method.PATCH, endpoint, data, callback, error_callback)


## Set the authentication token for subsequent requests
##
## @param token: JWT token from login/registration
func set_auth_token(token: String) -> void:
	_auth_token = token
	print("[HTTPClient] Auth token set")


## Clear the authentication token (logout)
func clear_auth_token() -> void:
	_auth_token = ""
	print("[HTTPClient] Auth token cleared")


## Get current auth token
func get_auth_token() -> String:
	return _auth_token


## Set the session cookie for subsequent requests
##
## @param cookie: Session cookie value (e.g., "coc_session=xxx")
func set_session_cookie(cookie: String) -> void:
	_session_cookie = cookie
	print("[HTTPClient] Session cookie set")


## Clear the session cookie (logout)
func clear_session_cookie() -> void:
	_session_cookie = ""
	print("[HTTPClient] Session cookie cleared")


## Get current session cookie
func get_session_cookie() -> String:
	return _session_cookie


## Check if authenticated
func is_authenticated() -> bool:
	return not _auth_token.is_empty() or not _session_cookie.is_empty()


## Internal: Make HTTP request
func _make_request(
	method: Method,
	endpoint: String,
	data: Dictionary,
	callback: Callable,
	error_callback: Callable
) -> void:
	# Create HTTPRequest node
	var http_request := HTTPRequest.new()
	add_child(http_request)

	# Generate request ID for tracking
	var request_id := "%s_%d" % [endpoint.get_file(), Time.get_ticks_msec()]
	_active_requests[request_id] = http_request

	# Build full URL
	var url := Settings.get_api_url() + endpoint

	# Build headers
	var headers := _build_headers()

	# Connect completion signal
	http_request.request_completed.connect(
		_on_request_completed.bind(request_id, callback, error_callback)
	)

	# Set timeout
	http_request.timeout = API_TIMEOUT

	# Make request based on method
	var method_int := _method_to_int(method)
	var body := ""

	if method in [Method.POST, Method.PUT, Method.PATCH]:
		body = JSON.stringify(data)

	print("[HTTPClient] %s %s" % [_method_to_string(method), url])
	if not body.is_empty():
		print("[HTTPClient] Body: %s" % body)

	var error := http_request.request(url, headers, method_int, body)

	if error != OK:
		push_error("[HTTPClient] Failed to initiate request: %s" % error)
		_cleanup_request(request_id)
		if error_callback.is_valid():
			error_callback.call(_create_error_response("Failed to initiate request", 0))


## Internal: Build request headers
func _build_headers() -> PackedStringArray:
	var headers := PackedStringArray([
		"Content-Type: application/json",
		"Accept: application/json"
	])

	# Add auth token if present (for JWT auth)
	if not _auth_token.is_empty():
		headers.append("Authorization: Bearer %s" % _auth_token)

	# Add session cookie if present (for cookie-based auth)
	if not _session_cookie.is_empty():
		headers.append("Cookie: %s" % _session_cookie)

	return headers


## Internal: Handle request completion
func _on_request_completed(
	result: int,
	response_code: int,
	headers: PackedStringArray,
	body: PackedByteArray,
	request_id: String,
	callback: Callable,
	error_callback: Callable
) -> void:
	print("[HTTPClient] Request completed: %s (status=%d)" % [request_id, response_code])

	# Extract Set-Cookie header if present
	_extract_session_cookie(headers)

	# Parse response body
	var body_string := body.get_string_from_utf8()

	# Handle network errors
	if result != HTTPRequest.RESULT_SUCCESS:
		push_error("[HTTPClient] Network error: %s" % _result_to_string(result))
		_cleanup_request(request_id)

		if error_callback.is_valid():
			error_callback.call(_create_error_response(_result_to_string(result), response_code))
		return

	# Parse JSON response
	var json := JSON.new()
	var parse_error := json.parse(body_string)

	if parse_error != OK:
		push_error("[HTTPClient] Failed to parse JSON: %s" % body_string)
		_cleanup_request(request_id)

		if error_callback.is_valid():
			error_callback.call(_create_error_response("Failed to parse JSON response", response_code))
		return

	# Create APIResponse
	var api_response: APIResponse = APIResponse.new(json.data)
	api_response.raw_response = body_string

	# Check for errors
	if api_response.has_error():
		print("[HTTPClient] API error: %s" % api_response.get_error_message())

		if error_callback.is_valid():
			error_callback.call(api_response)
		elif callback.is_valid():
			# Call regular callback even with error if no error callback
			callback.call(api_response)
	else:
		print("[HTTPClient] Success: %s" % request_id)

		if callback.is_valid():
			callback.call(api_response)

	_cleanup_request(request_id)


## Internal: Extract session cookie from response headers
func _extract_session_cookie(headers: PackedStringArray) -> void:
	for header in headers:
		# Look for Set-Cookie header
		if header.begins_with("Set-Cookie:") or header.begins_with("set-cookie:"):
			# Extract cookie value
			var cookie_string := header.substr(11).strip_edges()  # Remove "Set-Cookie:"

			# Look for coc_session cookie
			if cookie_string.begins_with("coc_session="):
				# Extract just the cookie name=value part (before semicolon)
				var cookie_parts := cookie_string.split(";")
				if cookie_parts.size() > 0:
					_session_cookie = cookie_parts[0].strip_edges()
					print("[HTTPClient] Session cookie extracted: %s" % _session_cookie)
					return


## Internal: Cleanup request resources
func _cleanup_request(request_id: String) -> void:
	if _active_requests.has(request_id):
		var http_request: HTTPRequest = _active_requests[request_id]
		http_request.queue_free()
		_active_requests.erase(request_id)


## Internal: Create error response
func _create_error_response(message: String, status_code: int) -> APIResponse:
	var error_data := {
		"app": "Code of Conquest",
		"version": "0.1.0",
		"status": status_code if status_code > 0 else 500,
		"timestamp": Time.get_datetime_string_from_system(),
		"result": null,
		"error": {
			"message": message,
			"code": "NETWORK_ERROR"
		},
		"meta": {}
	}
	return APIResponse.new(error_data)


## Internal: Convert Method enum to HTTPClient constant
func _method_to_int(method: Method) -> int:
	match method:
		Method.GET:
			return HTTPClient.METHOD_GET
		Method.POST:
			return HTTPClient.METHOD_POST
		Method.PUT:
			return HTTPClient.METHOD_PUT
		Method.DELETE:
			return HTTPClient.METHOD_DELETE
		Method.PATCH:
			return HTTPClient.METHOD_PATCH
		_:
			return HTTPClient.METHOD_GET


## Internal: Convert Method enum to string
func _method_to_string(method: Method) -> String:
	match method:
		Method.GET:
			return "GET"
		Method.POST:
			return "POST"
		Method.PUT:
			return "PUT"
		Method.DELETE:
			return "DELETE"
		Method.PATCH:
			return "PATCH"
		_:
			return "UNKNOWN"


## Internal: Convert HTTPRequest result to string
func _result_to_string(result: int) -> String:
	match result:
		HTTPRequest.RESULT_SUCCESS:
			return "Success"
		HTTPRequest.RESULT_CHUNKED_BODY_SIZE_MISMATCH:
			return "Chunked body size mismatch"
		HTTPRequest.RESULT_CANT_CONNECT:
			return "Can't connect to server"
		HTTPRequest.RESULT_CANT_RESOLVE:
			return "Can't resolve hostname"
		HTTPRequest.RESULT_CONNECTION_ERROR:
			return "Connection error"
		HTTPRequest.RESULT_TLS_HANDSHAKE_ERROR:
			return "TLS handshake error"
		HTTPRequest.RESULT_NO_RESPONSE:
			return "No response from server"
		HTTPRequest.RESULT_BODY_SIZE_LIMIT_EXCEEDED:
			return "Body size limit exceeded"
		HTTPRequest.RESULT_BODY_DECOMPRESS_FAILED:
			return "Body decompression failed"
		HTTPRequest.RESULT_REQUEST_FAILED:
			return "Request failed"
		HTTPRequest.RESULT_DOWNLOAD_FILE_CANT_OPEN:
			return "Can't open download file"
		HTTPRequest.RESULT_DOWNLOAD_FILE_WRITE_ERROR:
			return "Download file write error"
		HTTPRequest.RESULT_REDIRECT_LIMIT_REACHED:
			return "Redirect limit reached"
		HTTPRequest.RESULT_TIMEOUT:
			return "Request timeout"
		_:
			return "Unknown error (%d)" % result
