extends SceneTree

var failures: Array[String] = []


func _initialize() -> void:
	call_deferred("_run")


func _run() -> void:
	_test_registry_integrity()
	_test_definition_backed_behavior()
	_test_state_reference_validation()
	_test_id_round_trip()

	if failures.is_empty():
		print("[TEST] Simulation definitions passed")
		quit(0)
		return

	for failure in failures:
		push_error("[TEST] " + failure)
	quit(1)


func _test_registry_integrity() -> void:
	var errors := SimulationDefinitions.validate()
	_check(errors.is_empty(), "Definition registry should validate: %s" % [errors])

	var action_ids: Array[StringName] = []
	for definition in SimulationDefinitions.get_actions():
		action_ids.append(definition.action_id)
		_check(
			SimulationDefinitions.get_action(definition.action_id) == definition,
			"Action lookup should return '%s'" % definition.action_id
		)
	_check(action_ids.size() == 9, "Registry should contain all nine executable prototype actions")

	var profession_ids := SimulationDefinitions.get_profession_ids()
	_check(profession_ids.size() == 5, "Registry should contain all five prototype professions")
	for profession_id in profession_ids:
		_check(
			SimulationDefinitions.get_profession(profession_id) != null,
			"Profession lookup should return '%s'" % profession_id
		)


func _test_definition_backed_behavior() -> void:
	var npc := SimNPC.new(500, "DefinitionWorker", SimulationIds.PROFESSION_SCHOLAR, 5.0, 5.0)
	npc.set_task(SimulationIds.ACTION_STUDY)
	var study := SimulationDefinitions.get_action(SimulationIds.ACTION_STUDY)
	_check(
		is_equal_approx(npc.task_duration, study.default_duration),
		"SimNPC should use the action definition's default duration"
	)
	_check(
		study.preferred_profession_id == SimulationIds.PROFESSION_SCHOLAR,
		"Study should reference the stable scholar profession ID"
	)
	var gather := SimulationDefinitions.get_action(SimulationIds.ACTION_GATHER_FOOD)
	_check(
		(
			gather.target_type == SimulationIds.TARGET_RESOURCE
			and gather.resource_action_id == SimulationIds.ACTION_GATHER_FOOD
		),
		"Gather-food target metadata should come from its definition"
	)


func _test_state_reference_validation() -> void:
	var npc := SimNPC.new(501, "ValidationWorker", SimulationIds.PROFESSION_FARMER, 5.0, 5.0)
	var valid_data := NPCStateRecord.capture(npc).to_dictionary()
	var invalid_profession := valid_data.duplicate(true)
	invalid_profession["profession"] = "missing_profession"
	_check(
		NPCStateRecord.from_dictionary(invalid_profession) == null,
		"NPC state should reject an unknown profession ID"
	)
	var invalid_action := valid_data.duplicate(true)
	invalid_action["current_task"] = "missing_action"
	_check(
		NPCStateRecord.from_dictionary(invalid_action) == null,
		"NPC state should reject an unknown action ID"
	)


func _test_id_round_trip() -> void:
	var manager: Node = load("res://simulation/SimulationManager.gd").new()
	manager.debug_logs = false
	root.add_child(manager)
	manager.set_process(false)
	var json_state: String = manager.serialize_state()

	var restored: Node = load("res://simulation/SimulationManager.gd").new()
	restored.debug_logs = false
	root.add_child(restored)
	restored.set_process(false)
	_check(
		restored.restore_state_from_json(json_state),
		"Definition-backed IDs should restore from serialized state"
	)
	for npc in restored.npcs:
		_check(
			typeof(npc.profession) == TYPE_STRING_NAME, "Profession should restore as StringName"
		)
		_check(
			typeof(npc.current_task) == TYPE_STRING_NAME,
			"Current action should restore as StringName"
		)
		_check(
			SimulationDefinitions.get_profession(npc.profession) != null,
			"Restored profession should resolve through the registry"
		)

	manager.free()
	restored.free()


func _check(condition: bool, message: String) -> void:
	if not condition:
		failures.append(message)
