extends Node

const LANGUAGES_JSON_PATH := "res://localization/languages.json"
const SETTINGS_FILE = "user://settings.cfg"
const MAX_JSON_FILE_SIZE = 65536  # 64KB limit for languages.json
const MAX_SETTING_STRING_LENGTH = 10  # Max length for string settings like language code
# dev `user://`=`%APPDATA%\Godot\app_userdata\Skelly`
# prod `user://`=`%APPDATA%\Skelly\`

var settings: Dictionary = {}

var default_settings: Dictionary = {
	"master_volume": 0.50, "music_volume": 0.40, "sfx_volume": 0.50, "language": "en"
}

var languages_data: Dictionary = {}


func _ready() -> void:
	DebugManager.log_info("SettingsManager ready", "SettingsManager")
	load_languages()
	load_settings()


func load_settings() -> void:
	var config = ConfigFile.new()
	var load_result = config.load(SETTINGS_FILE)

	# Ensure settings dictionary exists
	if settings.is_empty():
		settings = default_settings.duplicate()

	if load_result == OK:
		for key in default_settings.keys():
			var loaded_value = config.get_value("settings", key, default_settings[key])
			# Validate loaded settings before applying
			if _validate_setting_value(key, loaded_value):
				settings[key] = loaded_value
			else:
				DebugManager.log_warn(
					(
						"Invalid setting value for '%s', using default: %s"
						% [key, str(default_settings[key])]
					),
					"SettingsManager"
				)
				settings[key] = default_settings[key]
		DebugManager.log_info("Settings loaded: " + str(settings), "SettingsManager")
	else:
		DebugManager.log_warn(
			"No settings file found (Error code: %d), using defaults" % load_result,
			"SettingsManager"
		)
		settings = default_settings.duplicate()

	# Apply settings with error handling
	_apply_all_settings()


func _apply_all_settings():
	DebugManager.log_info("Applying settings: " + str(settings), "SettingsManager")

	# Apply language setting
	if "language" in settings:
		TranslationServer.set_locale(settings["language"])

	# Apply audio settings with error checking
	var master_bus = AudioServer.get_bus_index("Master")
	var music_bus = AudioServer.get_bus_index("Music")
	var sfx_bus = AudioServer.get_bus_index("SFX")

	if master_bus >= 0 and "master_volume" in settings:
		AudioServer.set_bus_volume_db(master_bus, linear_to_db(settings["master_volume"]))
	else:
		DebugManager.log_warn(
			"Master audio bus not found or master_volume setting missing", "SettingsManager"
		)

	if music_bus >= 0 and "music_volume" in settings:
		AudioServer.set_bus_volume_db(music_bus, linear_to_db(settings["music_volume"]))
	else:
		DebugManager.log_warn(
			"Music audio bus not found or music_volume setting missing", "SettingsManager"
		)

	if sfx_bus >= 0 and "sfx_volume" in settings:
		AudioServer.set_bus_volume_db(sfx_bus, linear_to_db(settings["sfx_volume"]))
	else:
		DebugManager.log_warn(
			"SFX audio bus not found or sfx_volume setting missing", "SettingsManager"
		)


func save_settings():
	var config = ConfigFile.new()
	for key in settings.keys():
		config.set_value("settings", key, settings[key])

	var save_result = config.save(SETTINGS_FILE)
	if save_result != OK:
		DebugManager.log_error(
			"Failed to save settings (Error code: %d)" % save_result, "SettingsManager"
		)
		return false

	DebugManager.log_info("Settings saved: " + str(settings), "SettingsManager")
	return true


func get_setting(key: String):
	return settings.get(key)


func set_setting(key: String, value) -> bool:
	if not key in default_settings:
		DebugManager.log_error("Unknown setting key: " + key, "SettingsManager")
		return false

	# Validate value type and range based on key
	if not _validate_setting_value(key, value):
		DebugManager.log_error(
			"Invalid value for setting '%s': %s" % [key, str(value)], "SettingsManager"
		)
		return false

	settings[key] = value
	_apply_setting_side_effect(key, value)
	return true


func _validate_setting_value(key: String, value) -> bool:
	match key:
		"master_volume", "music_volume", "sfx_volume":
			# Enhanced numeric validation with NaN/Infinity checks
			if not (value is float or value is int):
				return false
			# Convert to float for validation
			var float_value = float(value)
			# Check for NaN and infinity
			if is_nan(float_value) or is_inf(float_value):
				DebugManager.log_warn(
					"Invalid float value for %s: %s" % [key, str(value)], "SettingsManager"
				)
				return false
			# Range validation
			return float_value >= 0.0 and float_value <= 1.0
		"language":
			if not value is String:
				return false
			# Prevent extremely long strings
			if value.length() > MAX_SETTING_STRING_LENGTH:
				DebugManager.log_warn(
					"Language code too long: %d characters" % value.length(), "SettingsManager"
				)
				return false
			# Check for valid characters (alphanumeric and common separators only)
			var regex = RegEx.new()
			regex.compile("^[a-zA-Z0-9_-]+$")
			if not regex.search(value):
				DebugManager.log_warn(
					"Language code contains invalid characters: %s" % value, "SettingsManager"
				)
				return false
			# Check if language is supported
			if languages_data.has("languages") and languages_data.languages is Dictionary:
				return value in languages_data.languages
			else:
				# Fallback to basic validation if languages not loaded
				return value in ["en", "ru"]

	# Default validation: accept if type matches default setting type
	var default_value = default_settings.get(key)
	if default_value == null:
		DebugManager.log_warn("Unknown setting key in validation: %s" % key, "SettingsManager")
		return false
	return typeof(value) == typeof(default_value)


func _apply_setting_side_effect(key: String, value) -> void:
	match key:
		"language":
			TranslationServer.set_locale(value)
		"master_volume":
			if AudioServer.get_bus_index("Master") >= 0:
				AudioServer.set_bus_volume_db(
					AudioServer.get_bus_index("Master"), linear_to_db(value)
				)
		"music_volume":
			AudioManager.update_music_volume(value)
		"sfx_volume":
			if AudioServer.get_bus_index("SFX") >= 0:
				AudioServer.set_bus_volume_db(AudioServer.get_bus_index("SFX"), linear_to_db(value))


func load_languages():
	var file = FileAccess.open(LANGUAGES_JSON_PATH, FileAccess.READ)
	if not file:
		var error_code = FileAccess.get_open_error()
		DebugManager.log_error(
			"Could not open languages.json (Error code: %d)" % error_code, "SettingsManager"
		)
		_load_default_languages()
		return

	# Check file size to prevent memory exhaustion
	var file_size = file.get_length()
	if file_size > MAX_JSON_FILE_SIZE:
		DebugManager.log_error(
			"Languages.json file too large: %d bytes (max %d)" % [file_size, MAX_JSON_FILE_SIZE],
			"SettingsManager"
		)
		file.close()
		_load_default_languages()
		return

	if file_size == 0:
		DebugManager.log_error("Languages.json file is empty", "SettingsManager")
		file.close()
		_load_default_languages()
		return

	var json_string = file.get_as_text()
	var file_error = file.get_error()
	file.close()

	if file_error != OK:
		DebugManager.log_error(
			"Error reading languages.json (Error code: %d)" % file_error, "SettingsManager"
		)
		_load_default_languages()
		return

	# Validate the JSON string is not empty
	if json_string.is_empty():
		DebugManager.log_error("Languages.json contains empty content", "SettingsManager")
		_load_default_languages()
		return

	var json = JSON.new()
	var parse_result = json.parse(json_string)
	if parse_result != OK:
		DebugManager.log_error(
			"JSON parsing failed at line %d: %s" % [json.error_line, json.error_string],
			"SettingsManager"
		)
		_load_default_languages()
		return

	if not json.data or not json.data is Dictionary:
		DebugManager.log_error("Invalid JSON data structure in languages.json", "SettingsManager")
		_load_default_languages()
		return

	# Validate the structure of the JSON data
	if not _validate_languages_structure(json.data):
		DebugManager.log_error("Languages.json structure validation failed", "SettingsManager")
		_load_default_languages()
		return

	languages_data = json.data
	DebugManager.log_info(
		"Languages loaded successfully: " + str(languages_data.languages.keys()), "SettingsManager"
	)


func _load_default_languages():
	# Fallback language data when JSON file fails to load
	languages_data = {
		"languages":
		{"en": {"name": "English", "flag": "🇺🇸"}, "ru": {"name": "Русский", "flag": "🇷🇺"}}
	}
	DebugManager.log_info("Default languages loaded as fallback", "SettingsManager")


func get_languages_data():
	return languages_data


func reset_settings_to_defaults() -> void:
	DebugManager.log_info("Resetting all settings to defaults", "SettingsManager")
	for key in default_settings.keys():
		settings[key] = default_settings[key]
		_apply_setting_side_effect(key, settings[key])
	var save_success = save_settings()
	if save_success:
		DebugManager.log_info("Settings reset completed successfully", "SettingsManager")
	else:
		DebugManager.log_error("Failed to save reset settings", "SettingsManager")


func _validate_languages_structure(data: Dictionary) -> bool:
	"""Validate the structure and content of languages.json data"""
	if not data.has("languages"):
		DebugManager.log_error("Languages.json missing 'languages' key", "SettingsManager")
		return false

	var languages = data["languages"]
	if not languages is Dictionary:
		DebugManager.log_error("'languages' is not a dictionary", "SettingsManager")
		return false

	if languages.is_empty():
		DebugManager.log_error("Languages dictionary is empty", "SettingsManager")
		return false

	# Validate each language entry
	for lang_code in languages.keys():
		if not lang_code is String:
			DebugManager.log_error(
				"Language code is not a string: %s" % str(lang_code), "SettingsManager"
			)
			return false

		if lang_code.length() > MAX_SETTING_STRING_LENGTH:
			DebugManager.log_error("Language code too long: %s" % lang_code, "SettingsManager")
			return false

		var lang_data = languages[lang_code]
		if not lang_data is Dictionary:
			DebugManager.log_error(
				"Language data for '%s' is not a dictionary" % lang_code, "SettingsManager"
			)
			return false

		# Validate required fields in language data
		if not lang_data.has("name") or not lang_data["name"] is String:
			DebugManager.log_error(
				"Language '%s' missing valid 'name' field" % lang_code, "SettingsManager"
			)
			return false

	return true
