extends GdUnitTestSuite

## NEO-73: allowed resource-node interact triggers inventory + skill refresh.

const IxClient := preload("res://scripts/interaction_request_client.gd")
const InventoryClient := preload("res://scripts/inventory_client.gd")
const SkillProgressionClient := preload("res://scripts/skill_progression_client.gd")
const Picker := preload("res://scripts/prototype_interactable_picker.gd")


class MockInteractTransport:
	extends Node
	signal request_completed(
		result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray
	)
	var last_body: String = ""
	var body_json: String = '{"schemaVersion":1,"allowed":true}'
	var response_code: int = 200

	func request(
		_url: String,
		_custom_headers: PackedStringArray = PackedStringArray(),
		_method: HTTPClient.Method = HTTPClient.METHOD_POST,
		request_data: String = ""
	) -> Error:
		last_body = request_data
		var body_bytes: PackedByteArray = body_json.to_utf8_buffer()
		request_completed.emit(
			HTTPRequest.RESULT_SUCCESS, response_code, PackedStringArray(), body_bytes
		)
		return OK


class SpySyncClient:
	extends Node
	var sync_calls: int = 0

	func request_sync_from_server() -> void:
		sync_calls += 1


class GatherRefreshHarness:
	extends Node
	var inventory_sync_calls: int = 0
	var skill_sync_calls: int = 0
	var pending_gather_interactable_id: String = ""
	var awaiting_inventory_finalize: bool = false
	var _inventory: SpySyncClient
	var _skill: SpySyncClient
	var _ix: Node

	func setup(parent: Node, ix_transport: Node) -> void:
		_inventory = SpySyncClient.new()
		_skill = SpySyncClient.new()
		_ix = IxClient.new()
		_ix.set("injected_http", ix_transport)
		parent.add_child(_ix)
		parent.add_child(_inventory)
		parent.add_child(_skill)
		_ix.connect("interaction_result_received", Callable(self, "_on_interaction_result"))

	func queue_resource_gather(interactable_id: String) -> void:
		pending_gather_interactable_id = interactable_id
		awaiting_inventory_finalize = false

	func _clear_gather_session() -> void:
		pending_gather_interactable_id = ""
		awaiting_inventory_finalize = false

	func _on_interaction_result(
		interactable_id: String, allowed: bool, _reason_code: String
	) -> void:
		if pending_gather_interactable_id.is_empty():
			return
		if interactable_id != pending_gather_interactable_id:
			if awaiting_inventory_finalize:
				return
			_clear_gather_session()
			return
		if not Picker.is_resource_node_id([], interactable_id):
			_clear_gather_session()
			return
		if not allowed:
			_clear_gather_session()
			return
		awaiting_inventory_finalize = true
		_inventory.request_sync_from_server()
		_skill.request_sync_from_server()

	func post_resource_alpha() -> void:
		queue_resource_gather("prototype_resource_node_alpha")
		_ix.call("post_interact_id", "prototype_resource_node_alpha")

	func emit_terminal_allowed() -> void:
		_ix.set("_current_interactable_id", "prototype_terminal")
		var body_bytes: PackedByteArray = '{"schemaVersion":1,"allowed":true}'.to_utf8_buffer()
		_ix.call("_on_request_completed", 0, 200, PackedStringArray(), body_bytes)


func test_allowed_resource_interact_triggers_both_refreshes() -> void:
	# Arrange
	var transport := MockInteractTransport.new()
	var harness := GatherRefreshHarness.new()
	auto_free(transport)
	auto_free(harness)
	add_child(harness)
	harness.setup(self, transport)
	monitor_signals(harness._ix)
	# Act
	harness.post_resource_alpha()
	# Assert
	await assert_signal(harness._ix).is_emitted(
		"interaction_result_received", "prototype_resource_node_alpha", true, ""
	)
	assert_that(harness._inventory.sync_calls).is_equal(1)
	assert_that(harness._skill.sync_calls).is_equal(1)


func test_denied_resource_interact_skips_refreshes() -> void:
	# Arrange
	var transport := MockInteractTransport.new()
	transport.body_json = '{"schemaVersion":1,"allowed":false,"reasonCode":"node_depleted"}'
	var harness := GatherRefreshHarness.new()
	auto_free(transport)
	auto_free(harness)
	add_child(harness)
	harness.setup(self, transport)
	# Act
	harness.post_resource_alpha()
	# Assert
	assert_that(harness._inventory.sync_calls).is_equal(0)
	assert_that(harness._skill.sync_calls).is_equal(0)


func test_terminal_result_clears_orphaned_gather_pending() -> void:
	# Arrange
	var transport := MockInteractTransport.new()
	var harness := GatherRefreshHarness.new()
	auto_free(transport)
	auto_free(harness)
	add_child(harness)
	harness.setup(self, transport)
	harness.queue_resource_gather("prototype_resource_node_alpha")
	# Act
	harness.emit_terminal_allowed()
	# Assert
	assert_that(harness.pending_gather_interactable_id).is_empty()
	assert_that(harness.awaiting_inventory_finalize).is_false()
	assert_that(harness._inventory.sync_calls).is_equal(0)
	assert_that(harness._skill.sync_calls).is_equal(0)


func test_terminal_after_gather_success_keeps_awaiting_inventory() -> void:
	# Arrange
	var transport := MockInteractTransport.new()
	var harness := GatherRefreshHarness.new()
	auto_free(transport)
	auto_free(harness)
	add_child(harness)
	harness.setup(self, transport)
	# Act
	harness.post_resource_alpha()
	harness.emit_terminal_allowed()
	# Assert
	assert_that(harness.awaiting_inventory_finalize).is_true()
	assert_that(harness.pending_gather_interactable_id).is_equal("prototype_resource_node_alpha")
	assert_that(harness._inventory.sync_calls).is_equal(1)
	assert_that(harness._skill.sync_calls).is_equal(1)
