# Tests for res://scripts/player.gd (minimal tree: CharacterBody3D + NavigationAgent3D).
extends GdUnitTestSuite

const PLAYER_SCRIPT: Script = preload("res://scripts/player.gd")


func _make_player() -> CharacterBody3D:
	var body := CharacterBody3D.new()
	var nav := NavigationAgent3D.new()
	nav.name = "NavigationAgent3D"
	body.add_child(nav)
	body.set_script(PLAYER_SCRIPT)
	auto_free(body)
	add_child(body)
	return body


func test_snap_to_server_resets_goal_position_and_velocity() -> void:
	# Arrange
	var p := _make_player()
	var target := Vector3(1.0, 0.9, -2.0)
	# Act
	p.snap_to_server(target)
	# Assert
	assert_that(p.global_position).is_equal(target)
	assert_that(p.velocity).is_equal(Vector3.ZERO)
	assert_that(p.get("_has_walk_goal")).is_false()


func test_set_authoritative_nav_goal_sets_goal_and_target() -> void:
	# Arrange
	var p := _make_player()
	p.set("_idle_anchor_active", true)
	var goal := Vector3(10.0, 0.0, 3.0)
	# Act
	p.set_authoritative_nav_goal(goal)
	# Assert
	assert_that(p.get("_has_walk_goal")).is_true()
	assert_that(p.get("_idle_anchor_active")).is_false()
	assert_that(p.get("_auth_walk_goal")).is_equal(goal)
	var nav: NavigationAgent3D = p.get_node("NavigationAgent3D") as NavigationAgent3D
	assert_that(nav.target_position).is_equal(goal)


func test_set_locomotion_wish_world_xz_normalizes_horizontal() -> void:
	# Arrange
	var p := _make_player()
	# Act
	p.call("set_locomotion_wish_world_xz", Vector3(3.0, 9.0, 4.0))
	# Assert
	var w: Vector3 = p.get("_locomotion_wish_world_xz") as Vector3
	assert_that(w.y).is_equal(0.0)
	assert_that(absf(w.length_squared() - 1.0)).is_less(0.0001)


func test_set_locomotion_wish_world_xz_zero_for_near_zero() -> void:
	# Arrange
	var p := _make_player()
	# Act
	p.call("set_locomotion_wish_world_xz", Vector3(0.0, 1.0, 0.0))
	# Assert
	assert_that(p.get("_locomotion_wish_world_xz") as Vector3).is_equal(Vector3.ZERO)


func test_clear_nav_goal_clears_velocity_and_nav_target() -> void:
	# Arrange
	var p := _make_player()
	p.velocity = Vector3(1.0, 0.0, 0.0)
	p.set_authoritative_nav_goal(Vector3(5.0, 0.0, 5.0))
	# Act
	p.clear_nav_goal()
	# Assert
	assert_that(p.velocity).is_equal(Vector3.ZERO)
	assert_that(p.get("_has_walk_goal")).is_false()
	assert_that(p.get("_idle_anchor_active")).is_false()
	var nav: NavigationAgent3D = p.get_node("NavigationAgent3D") as NavigationAgent3D
	assert_that(nav.target_position).is_equal(p.global_position)


func test_nav_goal_lifecycle_resets_vert_route_latch() -> void:
	# Arrange
	var p := _make_player()
	# Act
	p.set_authoritative_nav_goal(Vector3(1.0, 0.0, 2.0))
	p.set("_walk_vert_route_latched", true)
	p.clear_nav_goal()
	# Assert
	assert_that(p.get("_walk_vert_route_latched")).is_false()
	# Act
	p.set("_walk_vert_route_latched", true)
	p.set_authoritative_nav_goal(Vector3(3.0, 0.0, 4.0))
	# Assert
	assert_that(p.get("_walk_vert_route_latched")).is_false()
	# Act
	p.set("_walk_vert_route_latched", true)
	p.snap_to_server(Vector3(0.0, 0.9, 0.0))
	# Assert
	assert_that(p.get("_walk_vert_route_latched")).is_false()


func test_hold_idle_anchor_sets_anchor_first_then_clamps_xz() -> void:
	# Arrange
	var p := _make_player()
	p.global_position = Vector3(1.0, 0.5, 2.0)
	# Act
	p.call("_hold_idle_anchor")
	# Assert
	assert_that(p.get("_idle_anchor_active")).is_true()
	assert_that(p.get("_idle_anchor_xz")).is_equal(Vector2(1.0, 2.0))
	# Act
	p.global_position = Vector3(1.2, 0.7, 2.3)
	p.call("_hold_idle_anchor")
	# Assert
	assert_that(p.global_position).is_equal(Vector3(1.0, 0.5, 2.0))


func test_snap_to_server_clears_idle_anchor() -> void:
	# Arrange
	var p := _make_player()
	p.set("_idle_anchor_active", true)
	p.set("_idle_anchor_xz", Vector2(9.0, 9.0))
	# Act
	p.snap_to_server(Vector3(3.0, 0.9, 4.0))
	# Assert
	assert_that(p.get("_idle_anchor_active")).is_false()


func test_capsule_feet_y_uses_body_origin_minus_half_height() -> void:
	# Arrange
	# Act
	var feet_y: float = PLAYER_SCRIPT.capsule_feet_y(0.545678, 0.5)
	# Assert
	assert_that(absf(feet_y - 0.045678)).is_less(0.000001)


func test_vertical_arrival_error_uses_capsule_feet_height() -> void:
	# Arrange
	var goal_y := 0.045678
	var body_origin_y := 0.545678
	# Act
	var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_origin_y, 0.5)
	# Assert
	assert_that(absf(err)).is_less(0.000001)


func test_vertical_arrival_error_stays_large_for_origin_height_only_match() -> void:
	# Arrange
	var goal_y := 0.545678
	var body_origin_y := 0.545678
	# Act
	var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_origin_y, 0.5)
	# Assert
	assert_that(absf(err - 0.5)).is_less(0.000001)


func test_arrival_vert_err_large_for_step_goal_from_floor() -> void:
	# Arrange
	# Player at floor height (body Y=0.9). Goal at step surface (Y=0.3).
	# Using actual capsule bottom (CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS) the error
	# must exceed VERT_ARRIVE_EPS so the arrival check does NOT fire prematurely.
	var goal_y := 0.3
	var body_y := 0.9
	var full_half: float = (
		(
			PLAYER_SCRIPT.get("CAPSULE_HALF_HEIGHT") as float
			+ PLAYER_SCRIPT.get("PLAYER_CAPSULE_RADIUS")
		)
		as float
	)
	# Act
	var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_y, full_half)
	# Assert
	assert_that(err).is_greater(PLAYER_SCRIPT.get("VERT_ARRIVE_EPS") as float)


func test_arrival_vert_err_zero_when_standing_on_step() -> void:
	# Arrange
	# Player body centred at goal_y + total_half_height (i.e. actually standing on the step).
	# Error must be within VERT_ARRIVE_EPS so arrival fires correctly once the player is up.
	var goal_y := 0.3
	var full_half: float = (
		(
			PLAYER_SCRIPT.get("CAPSULE_HALF_HEIGHT") as float
			+ PLAYER_SCRIPT.get("PLAYER_CAPSULE_RADIUS")
		)
		as float
	)
	var body_y: float = goal_y + full_half
	# Act
	var err: float = PLAYER_SCRIPT.vertical_arrival_error(goal_y, body_y, full_half)
	# Assert
	assert_that(err).is_less_equal(PLAYER_SCRIPT.get("VERT_ARRIVE_EPS") as float)


func test_flat_floor_goal_is_not_below_feet_height_margin() -> void:
	# Arrange
	var goal_y := 0.0
	var body_origin_y := 0.4
	var descend_margin := PLAYER_SCRIPT.get("DESCEND_GOAL_Y_MARGIN") as float
	# Act
	var below_feet_margin: bool = (
		goal_y < PLAYER_SCRIPT.capsule_feet_y(body_origin_y, 0.5) - descend_margin
	)
	# Assert
	assert_that(below_feet_margin).is_false()


func test_step_assist_max_surface_delta_matches_navigation_mesh_agent_max_climb() -> void:
	# Arrange
	# Keep in sync with NavigationMesh `agent_max_climb` in `scenes/main.tscn`.
	# Act
	var delta: float = PLAYER_SCRIPT.get("WALK_STEP_ASSIST_MAX_SURFACE_DELTA") as float
	# Assert
	assert_that(delta).is_equal(0.35)
