class_name TestHelper
extends RefCounted

## Common test utilities and assertions for Skelly project testing
##
## Provides standardized testing functions, assertions, and utilities
## to ensure consistent test behavior across all test files.

## Test result tracking
static var tests_run = 0
static var tests_passed = 0
static var tests_failed = 0

## Performance tracking
static var test_start_time = 0.0
static var performance_data = {}

## Print test section header with consistent formatting
static func print_test_header(test_name: String):
	print("\n=== Testing %s ===" % test_name)
	tests_run = 0
	tests_passed = 0
	tests_failed = 0
	test_start_time = Time.get_unix_time_from_system()

## Print test section footer with results summary
static func print_test_footer(test_name: String):
	var end_time = Time.get_unix_time_from_system()
	var duration = end_time - test_start_time

	print("\n--- %s Results ---" % test_name)
	print("Tests Run: %d" % tests_run)
	print("Passed: %d" % tests_passed)
	print("Failed: %d" % tests_failed)
	print("Duration: %.3f seconds" % duration)

	if tests_failed == 0:
		print("✅ All tests PASSED")
	else:
		print("❌ %d tests FAILED" % tests_failed)

	print("=== %s Complete ===" % test_name)

## Assert that a condition is true
static func assert_true(condition: bool, message: String = ""):
	tests_run += 1
	if condition:
		tests_passed += 1
		print("✅ PASS: %s" % message)
	else:
		tests_failed += 1
		print("❌ FAIL: %s" % message)

## Assert that a condition is false
static func assert_false(condition: bool, message: String = ""):
	assert_true(not condition, message)

## Assert that two values are equal
static func assert_equal(expected, actual, message: String = ""):
	var condition = expected == actual
	var full_message = message
	if not full_message.is_empty():
		full_message += " "
	full_message += "(Expected: %s, Got: %s)" % [str(expected), str(actual)]
	assert_true(condition, full_message)

## Assert that two values are not equal
static func assert_not_equal(expected, actual, message: String = ""):
	var condition = expected != actual
	var full_message = message
	if not full_message.is_empty():
		full_message += " "
	full_message += "(Should not equal: %s, Got: %s)" % [str(expected), str(actual)]
	assert_true(condition, full_message)

## Assert that a value is null
static func assert_null(value, message: String = ""):
	assert_true(value == null, message + " (Should be null, got: %s)" % str(value))

## Assert that a value is not null
static func assert_not_null(value, message: String = ""):
	assert_true(value != null, message + " (Should not be null)")

## Assert that a value is within a range
static func assert_in_range(value: float, min_val: float, max_val: float, message: String = ""):
	var condition = value >= min_val and value <= max_val
	var full_message = "%s (Value: %f, Range: %f-%f)" % [message, value, min_val, max_val]
	assert_true(condition, full_message)

## Assert that two floating-point values are approximately equal (with tolerance)
static func assert_float_equal(expected: float, actual: float, tolerance: float = 0.0001, message: String = ""):
	# Handle special cases: both infinity, both negative infinity, both NaN
	if is_inf(expected) and is_inf(actual):
		var infinity_condition = (expected > 0) == (actual > 0)  # Same sign of infinity
		var infinity_message = "%s (Both infinity values: Expected: %f, Got: %f)" % [message, expected, actual]
		assert_true(infinity_condition, infinity_message)
		return

	if is_nan(expected) and is_nan(actual):
		var nan_message = "%s (Both NaN values: Expected: %f, Got: %f)" % [message, expected, actual]
		assert_true(true, nan_message)  # Both NaN is considered equal
		return

	# Normal floating-point comparison
	var difference = abs(expected - actual)
	var tolerance_condition = difference <= tolerance
	var comparison_message = "%s (Expected: %f, Got: %f, Difference: %f, Tolerance: %f)" % [message, expected, actual, difference, tolerance]
	assert_true(tolerance_condition, comparison_message)

## Assert that an array contains a specific value
static func assert_contains(array: Array, value, message: String = ""):
	var condition = value in array
	var full_message = "%s (Array: %s, Looking for: %s)" % [message, str(array), str(value)]
	assert_true(condition, full_message)

## Assert that an array does not contain a specific value
static func assert_not_contains(array: Array, value, message: String = ""):
	var condition = not (value in array)
	var full_message = "%s (Array: %s, Should not contain: %s)" % [message, str(array), str(value)]
	assert_true(condition, full_message)

## Assert that a dictionary has a specific key
static func assert_has_key(dict: Dictionary, key, message: String = ""):
	var condition = dict.has(key)
	var full_message = "%s (Dictionary keys: %s, Looking for: %s)" % [message, str(dict.keys()), str(key)]
	assert_true(condition, full_message)

## Assert that a file exists
static func assert_file_exists(path: String, message: String = ""):
	var condition = FileAccess.file_exists(path)
	var full_message = "%s (Path: %s)" % [message, path]
	assert_true(condition, full_message)

## Assert that a file does not exist
static func assert_file_not_exists(path: String, message: String = ""):
	var condition = not FileAccess.file_exists(path)
	var full_message = "%s (Path: %s)" % [message, path]
	assert_true(condition, full_message)

## Performance testing - start timing
static func start_performance_test(test_id: String):
	performance_data[test_id] = Time.get_unix_time_from_system()

## Performance testing - end timing and validate
static func end_performance_test(test_id: String, max_duration_ms: float, message: String = ""):
	if not performance_data.has(test_id):
		assert_true(false, "Performance test '%s' was not started" % test_id)
		return

	var start_time = performance_data[test_id]
	var end_time = Time.get_unix_time_from_system()
	var duration_ms = (end_time - start_time) * 1000.0

	var condition = duration_ms <= max_duration_ms
	var full_message = "%s (Duration: %.2fms, Max: %.2fms)" % [message, duration_ms, max_duration_ms]
	assert_true(condition, full_message)

	performance_data.erase(test_id)

## Create a temporary test file with content
static func create_temp_file(filename: String, content: String = "") -> String:
	var temp_path = "user://test_" + filename
	var file = FileAccess.open(temp_path, FileAccess.WRITE)
	if file:
		file.store_string(content)
		file.close()
	return temp_path

## Clean up temporary test file
static func cleanup_temp_file(path: String):
	if FileAccess.file_exists(path):
		DirAccess.remove_absolute(path)

## Create invalid JSON content for testing
static func create_invalid_json() -> String:
	return '{"invalid": json, missing_quotes: true, trailing_comma: true,}'

## Create valid test JSON content
static func create_valid_json() -> String:
	return '{"test_key": "test_value", "test_number": 42, "test_bool": true}'

## Wait for a specific number of frames
static func wait_frames(frames: int, node: Node):
	for i in range(frames):
		await node.get_tree().process_frame

## Mock a simple function call counter
class MockCallCounter:
	var call_count := 0
	var last_args := []

	func call_function(args: Array = []):
		call_count += 1
		last_args = args.duplicate()

	func reset():
		call_count = 0
		last_args.clear()

## Create a mock call counter for testing
static func create_mock_counter() -> MockCallCounter:
	return MockCallCounter.new()

## Validate that an object has expected properties
static func assert_has_properties(object: Object, properties: Array, message: String = ""):
	for property in properties:
		var condition = property in object
		var full_message = "%s - Missing property: %s" % [message, property]
		assert_true(condition, full_message)

## Validate that an object has expected methods
static func assert_has_methods(object: Object, methods: Array, message: String = ""):
	for method in methods:
		var condition = object.has_method(method)
		var full_message = "%s - Missing method: %s" % [message, method]
		assert_true(condition, full_message)

## Print a test step with consistent formatting
static func print_step(step_name: String):
	print("\n--- Test: %s ---" % step_name)