extends SceneTree

## Test suite for Match3Gameplay
##
## Tests grid initialization, match detection, and scoring system.

const TestHelperClass = preload("res://tests/helpers/TestHelper.gd")

var match3_scene: PackedScene
var match3_instance: Node2D
var test_viewport: SubViewport


func _initialize():
	# Wait for autoloads to initialize
	await process_frame
	await process_frame

	run_tests()

	# Exit after tests complete
	quit()


func run_tests():
	TestHelperClass.print_test_header("Match3 Gameplay")

	# Setup test environment
	setup_test_environment()

	# Run test suites
	test_basic_functionality()
	test_constants_and_safety_limits()
	test_grid_initialization()
	test_grid_layout_calculation()
	test_state_management()
	test_match_detection()
	test_scoring_system()
	test_input_validation()
	test_memory_safety()
	test_performance_requirements()

	# Cleanup
	cleanup_tests()

	TestHelperClass.print_test_footer("Match3 Gameplay")


func setup_test_environment():
	TestHelperClass.print_step("Test Environment Setup")

	# Load Match3 scene
	match3_scene = load("res://scenes/game/gameplays/match3_gameplay.tscn")
	TestHelperClass.assert_not_null(match3_scene, "Match3 scene loads successfully")

	# Create test viewport for isolated testing
	test_viewport = SubViewport.new()
	test_viewport.size = Vector2i(800, 600)
	root.add_child(test_viewport)

	# Instance Match3 in test viewport
	if match3_scene:
		match3_instance = match3_scene.instantiate()
		test_viewport.add_child(match3_instance)
		TestHelperClass.assert_not_null(match3_instance, "Match3 instance created successfully")

		# Wait for initialization
		await process_frame
		await process_frame


func test_basic_functionality():
	TestHelperClass.print_step("Basic Functionality")

	if not match3_instance:
		TestHelperClass.assert_true(false, "Match3 instance not available for testing")
		return

	# Test that Match3 has expected properties
	var expected_properties = [
		"GRID_SIZE", "TILE_TYPES", "grid", "current_state", "selected_tile", "cursor_position"
	]
	for prop in expected_properties:
		TestHelperClass.assert_true(prop in match3_instance, "Match3 has property: " + prop)

	# Test that Match3 has expected methods
	var expected_methods = [
		"_has_match_at", "_check_for_matches", "_get_match_line", "_clear_matches"
	]
	TestHelperClass.assert_has_methods(match3_instance, expected_methods, "Match3 gameplay methods")

	# Test signals
	TestHelperClass.assert_true(
		match3_instance.has_signal("score_changed"), "Match3 has score_changed signal"
	)
	TestHelperClass.assert_true(
		match3_instance.has_signal("grid_state_loaded"), "Match3 has grid_state_loaded signal"
	)


func test_constants_and_safety_limits():
	TestHelperClass.print_step("Constants and Safety Limits")

	if not match3_instance:
		return

	# Test safety constants exist
	TestHelperClass.assert_true("MAX_GRID_SIZE" in match3_instance, "MAX_GRID_SIZE constant exists")
	TestHelperClass.assert_true("MAX_TILE_TYPES" in match3_instance, "MAX_TILE_TYPES constant exists")
	TestHelperClass.assert_true(
		"MAX_CASCADE_ITERATIONS" in match3_instance, "MAX_CASCADE_ITERATIONS constant exists"
	)
	TestHelperClass.assert_true("MIN_GRID_SIZE" in match3_instance, "MIN_GRID_SIZE constant exists")
	TestHelperClass.assert_true("MIN_TILE_TYPES" in match3_instance, "MIN_TILE_TYPES constant exists")

	# Test safety limit values are reasonable
	TestHelperClass.assert_equal(15, match3_instance.MAX_GRID_SIZE, "MAX_GRID_SIZE is reasonable")
	TestHelperClass.assert_equal(10, match3_instance.MAX_TILE_TYPES, "MAX_TILE_TYPES is reasonable")
	TestHelperClass.assert_equal(
		20, match3_instance.MAX_CASCADE_ITERATIONS, "MAX_CASCADE_ITERATIONS prevents infinite loops"
	)
	TestHelperClass.assert_equal(3, match3_instance.MIN_GRID_SIZE, "MIN_GRID_SIZE is reasonable")
	TestHelperClass.assert_equal(3, match3_instance.MIN_TILE_TYPES, "MIN_TILE_TYPES is reasonable")

	# Test current values are within safety limits
	TestHelperClass.assert_in_range(
		match3_instance.GRID_SIZE.x,
		match3_instance.MIN_GRID_SIZE,
		match3_instance.MAX_GRID_SIZE,
		"Grid width within safety limits"
	)
	TestHelperClass.assert_in_range(
		match3_instance.GRID_SIZE.y,
		match3_instance.MIN_GRID_SIZE,
		match3_instance.MAX_GRID_SIZE,
		"Grid height within safety limits"
	)
	TestHelperClass.assert_in_range(
		match3_instance.TILE_TYPES,
		match3_instance.MIN_TILE_TYPES,
		match3_instance.MAX_TILE_TYPES,
		"Tile types within safety limits"
	)

	# Test timing constants
	TestHelperClass.assert_true(
		"CASCADE_WAIT_TIME" in match3_instance, "CASCADE_WAIT_TIME constant exists"
	)
	TestHelperClass.assert_true(
		"SWAP_ANIMATION_TIME" in match3_instance, "SWAP_ANIMATION_TIME constant exists"
	)
	TestHelperClass.assert_true(
		"TILE_DROP_WAIT_TIME" in match3_instance, "TILE_DROP_WAIT_TIME constant exists"
	)


func test_grid_initialization():
	TestHelperClass.print_step("Grid Initialization")

	if not match3_instance:
		return

	# Test grid structure
	TestHelperClass.assert_not_null(match3_instance.grid, "Grid array is initialized")
	TestHelperClass.assert_true(match3_instance.grid is Array, "Grid is Array type")

	# Test grid dimensions
	var expected_height = match3_instance.GRID_SIZE.y
	var expected_width = match3_instance.GRID_SIZE.x

	TestHelperClass.assert_equal(expected_height, match3_instance.grid.size(), "Grid has correct height")

	# Test each row has correct width
	for y in range(match3_instance.grid.size()):
		if y < expected_height:
			TestHelperClass.assert_equal(
				expected_width, match3_instance.grid[y].size(), "Grid row %d has correct width" % y
			)

	# Test tiles are properly instantiated
	var tile_count = 0
	var valid_tile_count = 0

	for y in range(match3_instance.grid.size()):
		for x in range(match3_instance.grid[y].size()):
			var tile = match3_instance.grid[y][x]
			tile_count += 1

			if tile and is_instance_valid(tile):
				valid_tile_count += 1
				TestHelperClass.assert_true(
					"tile_type" in tile, "Tile at (%d,%d) has tile_type property" % [x, y]
				)
				TestHelperClass.assert_true(
					"grid_position" in tile, "Tile at (%d,%d) has grid_position property" % [x, y]
				)

				# Test tile type is within valid range
				if "tile_type" in tile:
					TestHelperClass.assert_in_range(
						tile.tile_type,
						0,
						match3_instance.TILE_TYPES - 1,
						"Tile type in valid range"
					)

	TestHelperClass.assert_equal(tile_count, valid_tile_count, "All grid positions have valid tiles")


func test_grid_layout_calculation():
	TestHelperClass.print_step("Grid Layout Calculation")

	if not match3_instance:
		return

	# Test tile size calculation
	TestHelperClass.assert_true(match3_instance.tile_size > 0, "Tile size is positive")
	TestHelperClass.assert_true(
		match3_instance.tile_size <= 200, "Tile size is reasonable (not too large)"
	)

	# Test grid offset
	TestHelperClass.assert_not_null(match3_instance.grid_offset, "Grid offset is set")
	TestHelperClass.assert_true(match3_instance.grid_offset.x >= 0, "Grid offset X is non-negative")
	TestHelperClass.assert_true(match3_instance.grid_offset.y >= 0, "Grid offset Y is non-negative")

	# Test layout constants
	TestHelperClass.assert_equal(0.8, match3_instance.SCREEN_WIDTH_USAGE, "Screen width usage constant")
	TestHelperClass.assert_equal(
		0.7, match3_instance.SCREEN_HEIGHT_USAGE, "Screen height usage constant"
	)
	TestHelperClass.assert_equal(50.0, match3_instance.GRID_LEFT_MARGIN, "Grid left margin constant")
	TestHelperClass.assert_equal(50.0, match3_instance.GRID_TOP_MARGIN, "Grid top margin constant")


func test_state_management():
	TestHelperClass.print_step("State Management")

	if not match3_instance:
		return

	# Test GameState enum exists and has expected values
	var _game_state_class = match3_instance.get_script().get_global_class()
	TestHelperClass.assert_true("GameState" in match3_instance, "GameState enum accessible")

	# Test current state is valid
	TestHelperClass.assert_not_null(match3_instance.current_state, "Current state is set")

	# Test initialization flags
	TestHelperClass.assert_true("grid_initialized" in match3_instance, "Grid initialized flag exists")
	TestHelperClass.assert_true(match3_instance.grid_initialized, "Grid is marked as initialized")

	# Test instance ID for debugging
	TestHelperClass.assert_true("instance_id" in match3_instance, "Instance ID exists for debugging")
	TestHelperClass.assert_true(
		match3_instance.instance_id.begins_with("Match3_"), "Instance ID has correct format"
	)


func test_match_detection():
	TestHelperClass.print_step("Match Detection Logic")

	if not match3_instance:
		return

	# Test match detection methods exist and can be called safely
	TestHelperClass.assert_true(
		match3_instance.has_method("_has_match_at"), "_has_match_at method exists"
	)
	TestHelperClass.assert_true(
		match3_instance.has_method("_check_for_matches"), "_check_for_matches method exists"
	)
	TestHelperClass.assert_true(
		match3_instance.has_method("_get_match_line"), "_get_match_line method exists"
	)

	# Test boundary checking with invalid positions
	var invalid_positions = [
		Vector2i(-1, 0),
		Vector2i(0, -1),
		Vector2i(match3_instance.GRID_SIZE.x, 0),
		Vector2i(0, match3_instance.GRID_SIZE.y),
		Vector2i(100, 100)
	]

	for pos in invalid_positions:
		var result = match3_instance._has_match_at(pos)
		TestHelperClass.assert_false(result, "Invalid position (%d,%d) returns false" % [pos.x, pos.y])

	# Test valid positions don't crash
	for y in range(min(3, match3_instance.GRID_SIZE.y)):
		for x in range(min(3, match3_instance.GRID_SIZE.x)):
			var pos = Vector2i(x, y)
			var result = match3_instance._has_match_at(pos)
			TestHelperClass.assert_true(
				result is bool, "Valid position (%d,%d) returns boolean" % [x, y]
			)


func test_scoring_system():
	TestHelperClass.print_step("Scoring System")

	if not match3_instance:
		return

	# Test scoring formula constants and logic
	# The scoring system uses: 3 gems = 3 points, 4+ gems = n + (n-2) points

	# Test that the match3 instance can handle scoring (indirectly through clearing matches)
	TestHelperClass.assert_true(
		match3_instance.has_method("_clear_matches"), "Scoring system method exists"
	)

	# Test that score_changed signal exists
	TestHelperClass.assert_true(
		match3_instance.has_signal("score_changed"), "Score changed signal exists"
	)

	# Test scoring formula logic (based on the documented formula)
	var test_scores = {3: 3, 4: 6, 5: 8, 6: 10}  # 3 gems = exactly 3 points  # 4 gems = 4 + (4-2) = 6 points  # 5 gems = 5 + (5-2) = 8 points  # 6 gems = 6 + (6-2) = 10 points

	for match_size in test_scores.keys():
		var expected_score = test_scores[match_size]
		var calculated_score: int
		if match_size == 3:
			calculated_score = 3
		else:
			calculated_score = match_size + max(0, match_size - 2)

		TestHelperClass.assert_equal(
			expected_score, calculated_score, "Scoring formula correct for %d gems" % match_size
		)


func test_input_validation():
	TestHelperClass.print_step("Input Validation")

	if not match3_instance:
		return

	# Test cursor position bounds
	TestHelperClass.assert_not_null(match3_instance.cursor_position, "Cursor position is initialized")
	TestHelperClass.assert_true(
		match3_instance.cursor_position is Vector2i, "Cursor position is Vector2i type"
	)

	# Test keyboard navigation flag
	TestHelperClass.assert_true(
		"keyboard_navigation_enabled" in match3_instance, "Keyboard navigation flag exists"
	)
	TestHelperClass.assert_true(
		match3_instance.keyboard_navigation_enabled is bool, "Keyboard navigation flag is boolean"
	)

	# Test selected tile safety
	# selected_tile can be null initially, which is valid
	if match3_instance.selected_tile:
		TestHelperClass.assert_true(
			is_instance_valid(match3_instance.selected_tile), "Selected tile is valid if not null"
		)


func test_memory_safety():
	TestHelperClass.print_step("Memory Safety")

	if not match3_instance:
		return

	# Test grid integrity validation
	TestHelperClass.assert_true(
		match3_instance.has_method("_validate_grid_integrity"),
		"Grid integrity validation method exists"
	)

	# Test tile validity checking
	for y in range(min(3, match3_instance.grid.size())):
		for x in range(min(3, match3_instance.grid[y].size())):
			var tile = match3_instance.grid[y][x]
			if tile:
				TestHelperClass.assert_true(
					is_instance_valid(tile), "Grid tile at (%d,%d) is valid instance" % [x, y]
				)
				TestHelperClass.assert_true(
					tile.get_parent() == match3_instance, "Tile properly parented to Match3"
				)

	# Test position validation
	TestHelperClass.assert_true(
		match3_instance.has_method("_is_valid_grid_position"), "Position validation method exists"
	)

	# Test safe tile access patterns exist
	# The Match3 code uses comprehensive bounds checking and null validation
	TestHelperClass.assert_true(true, "Memory safety patterns implemented in Match3 code")


func test_performance_requirements():
	TestHelperClass.print_step("Performance Requirements")

	if not match3_instance:
		return

	# Test grid size is within performance limits
	var total_tiles = match3_instance.GRID_SIZE.x * match3_instance.GRID_SIZE.y
	TestHelperClass.assert_true(total_tiles <= 225, "Total tiles within performance limit (15x15=225)")

	# Test cascade iteration limit prevents infinite loops
	TestHelperClass.assert_equal(
		20,
		match3_instance.MAX_CASCADE_ITERATIONS,
		"Cascade iteration limit prevents infinite loops"
	)

	# Test timing constants are reasonable for 60fps gameplay
	TestHelperClass.assert_true(
		match3_instance.CASCADE_WAIT_TIME >= 0.05, "Cascade wait time allows for smooth animation"
	)
	TestHelperClass.assert_true(
		match3_instance.SWAP_ANIMATION_TIME <= 0.5, "Swap animation time is responsive"
	)
	TestHelperClass.assert_true(
		match3_instance.TILE_DROP_WAIT_TIME <= 0.3, "Tile drop wait time is responsive"
	)

	# Test grid initialization performance
	TestHelperClass.start_performance_test("grid_access")
	for y in range(min(5, match3_instance.grid.size())):
		for x in range(min(5, match3_instance.grid[y].size())):
			var tile = match3_instance.grid[y][x]
			if tile and "tile_type" in tile:
				var _tile_type = tile.tile_type
	TestHelperClass.end_performance_test("grid_access", 10.0, "Grid access performance within limits")


func cleanup_tests():
	TestHelperClass.print_step("Cleanup")

	# Clean up Match3 instance
	if match3_instance and is_instance_valid(match3_instance):
		match3_instance.queue_free()

	# Clean up test viewport
	if test_viewport and is_instance_valid(test_viewport):
		test_viewport.queue_free()

	# Wait for cleanup
	await process_frame

	TestHelperClass.assert_true(true, "Test cleanup completed")
