extends SceneTree

const BattleStateScript := preload("res://scripts/core/battle_state.gd")
const BattleSceneScript := preload("res://scripts/scenes/battle_scene.gd")


func _init() -> void:
	var failures: Array[String] = []
	_check_core_unequip_contract(failures)
	_check_unequip_guards(failures)
	_check_armor_bonus_removal(failures)
	_check_snapshot_reload_and_sell_stock(failures)
	_check_unequip_ui_entries(failures)

	if failures.is_empty():
		print("equipment unequip smoke ok")
		quit(0)
		return

	for failure in failures:
		push_error(failure)
	quit(1)


func _check_core_unequip_contract(failures: Array[String]) -> void:
	var state = _loaded_state(failures, "core unequip")
	if state == null:
		return

	var cao_cao: Dictionary = state.get_unit("cao_cao")
	var before_atk := int(cao_cao.get("atk", 0))
	var before_inventory: Dictionary = state.get_inventory_snapshot()
	var weapon_item: Dictionary = state.get_item_def("bronze_sword")
	var weapon_bonus := int(weapon_item.get("bonuses", {}).get("atk", 0))

	if not state.get_equipped_equipment_slots("cao_cao").has("weapon"):
		failures.append("Cao Cao should expose an equipped weapon slot")
	if not state.try_unequip_item("cao_cao", "weapon"):
		failures.append("Cao Cao should be able to unequip Bronze Sword before acting")
		return

	var after_unit: Dictionary = state.get_unit("cao_cao")
	var after_equipment: Dictionary = state.get_equipment_snapshot("cao_cao")
	var after_inventory: Dictionary = state.get_inventory_snapshot()
	if after_equipment.has("weapon"):
		failures.append("weapon slot should be removed after unequip: %s" % str(after_equipment))
	if int(after_inventory.get("bronze_sword", 0)) != int(before_inventory.get("bronze_sword", 0)) + 1:
		failures.append("unequip should return Bronze Sword to inventory")
	if int(after_unit.get("atk", 0)) != before_atk - weapon_bonus:
		failures.append("unequip should remove weapon ATK bonus: before %d after %d bonus %d" % [
			before_atk,
			int(after_unit.get("atk", 0)),
			weapon_bonus
		])
	if int(after_unit.get("range", 0)) != 1 or int(after_unit.get("min_range", 0)) != 1:
		failures.append("unequipped Cao Cao should return to class attack range 1-1")

	var inventory_after_first: Dictionary = state.get_inventory_snapshot()
	if state.try_unequip_item("cao_cao", "weapon"):
		failures.append("unequipping an empty weapon slot should fail")
	var inventory_after_repeat: Dictionary = state.get_inventory_snapshot()
	if int(inventory_after_repeat.get("bronze_sword", 0)) != int(inventory_after_first.get("bronze_sword", 0)):
		failures.append("failed repeat unequip should not duplicate inventory")

	if not state.try_equip_item("cao_cao", "bronze_sword"):
		failures.append("returned Bronze Sword should be equippable again")
	var reequip_unit: Dictionary = state.get_unit("cao_cao")
	var reequip_inventory: Dictionary = state.get_inventory_snapshot()
	if int(reequip_unit.get("atk", 0)) != before_atk:
		failures.append("re-equipping should restore weapon ATK")
	if int(reequip_inventory.get("bronze_sword", 0)) != int(before_inventory.get("bronze_sword", 0)):
		failures.append("re-equipping should consume the returned weapon stock")


func _check_unequip_guards(failures: Array[String]) -> void:
	var state = _loaded_state(failures, "unequip guards")
	if state == null:
		return

	var before_equipment: Dictionary = state.get_equipment_snapshot("cao_cao")
	var before_inventory: Dictionary = state.get_inventory_snapshot()

	var cao_cao: Dictionary = state.get_unit("cao_cao")
	cao_cao["moved"] = true
	if state.try_unequip_item("cao_cao", "weapon"):
		failures.append("moved units should not be able to unequip")
	cao_cao["moved"] = false
	cao_cao["acted"] = true
	if state.try_unequip_item("cao_cao", "weapon"):
		failures.append("acted units should not be able to unequip")
	cao_cao["acted"] = false

	var enemy: Dictionary = state.get_unit("yellow_turban_1")
	enemy["equipment"] = {"weapon": "bronze_sword"}
	if state.try_unequip_item("yellow_turban_1", "weapon"):
		failures.append("enemy units should not be able to unequip")

	var protected_state = BattleStateScript.new()
	if protected_state.load_battle("res://data/scenarios/007_xian_emperor_escort.json"):
		var envoy: Dictionary = protected_state.get_unit("protected_envoy")
		envoy["equipment"] = {"weapon": "bronze_sword"}
		if protected_state.try_unequip_item("protected_envoy", "weapon"):
			failures.append("non-controllable protected units should not be able to unequip")
	else:
		failures.append("could not load protected-unit scenario for unequip guard")

	if state.get_equipment_snapshot("cao_cao") != before_equipment:
		failures.append("failed guard unequips should not alter Cao Cao equipment")
	if state.get_inventory_snapshot() != before_inventory:
		failures.append("failed guard unequips should not alter inventory")


func _check_armor_bonus_removal(failures: Array[String]) -> void:
	var state = _loaded_state(failures, "armor bonus removal")
	if state == null:
		return
	var cao_cao: Dictionary = state.get_unit("cao_cao")
	var base_max_mp := int(cao_cao.get("max_mp", 0))
	var base_mp := int(cao_cao.get("mp", 0))
	var base_def := int(cao_cao.get("def", 0))
	var base_int := int(cao_cao.get("int", 0))

	state.set_inventory_snapshot({"black_tortoise_robe": 1})
	if not state.try_equip_item("cao_cao", "black_tortoise_robe"):
		failures.append("Cao Cao should be able to equip Black Tortoise Robe for armor unequip test")
		return
	var equipped: Dictionary = state.get_unit("cao_cao")
	if int(equipped.get("max_mp", 0)) != base_max_mp + 2:
		failures.append("Black Tortoise Robe should add max MP")
	if int(equipped.get("def", 0)) != base_def + 2:
		failures.append("Black Tortoise Robe should replace Cloth Robe with net DEF +2")
	if int(equipped.get("int", 0)) != base_int + 1:
		failures.append("Black Tortoise Robe should add INT")

	if not state.try_unequip_item("cao_cao", "armor"):
		failures.append("Cao Cao should be able to unequip armor")
		return
	var unequipped: Dictionary = state.get_unit("cao_cao")
	if int(unequipped.get("max_mp", 0)) != base_max_mp:
		failures.append("armor unequip should remove max MP bonus")
	if int(unequipped.get("mp", 0)) != mini(base_mp, base_max_mp):
		failures.append("armor unequip should clamp current MP to restored max")
	if int(unequipped.get("def", 0)) != base_def - 1:
		failures.append("empty armor slot should remove all armor DEF bonus")
	if int(unequipped.get("int", 0)) != base_int:
		failures.append("armor unequip should remove INT bonus")
	var inventory: Dictionary = state.get_inventory_snapshot()
	if int(inventory.get("black_tortoise_robe", 0)) != 1:
		failures.append("armor unequip should return Black Tortoise Robe to inventory")


func _check_snapshot_reload_and_sell_stock(failures: Array[String]) -> void:
	var state = _loaded_state(failures, "snapshot reload")
	if state == null:
		return
	if not state.try_unequip_item("cao_cao", "weapon"):
		failures.append("could not unequip weapon before snapshot reload")
		return

	var roster_snapshot: Dictionary = state.get_player_roster_snapshot()
	var inventory_snapshot: Dictionary = state.get_inventory_snapshot()
	var reloaded = BattleStateScript.new()
	if not reloaded.load_battle("res://data/scenarios/001_yellow_turbans.json", roster_snapshot, inventory_snapshot):
		failures.append("could not reload battle from unequipped roster snapshot")
		return
	var reloaded_equipment: Dictionary = reloaded.get_equipment_snapshot("cao_cao")
	if reloaded_equipment.has("weapon"):
		failures.append("roster snapshot reload should keep weapon slot empty: %s" % str(reloaded_equipment))
	if int(reloaded.get_inventory_snapshot().get("bronze_sword", 0)) != int(inventory_snapshot.get("bronze_sword", 0)):
		failures.append("inventory snapshot reload should keep returned weapon stock")

	var scene = BattleSceneScript.new()
	scene._create_hud()
	scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json", roster_snapshot, inventory_snapshot)
	scene.campaign_state.inventory = inventory_snapshot.duplicate(true)
	var sellable: Array = scene._get_sellable_item_ids()
	if not sellable.has("bronze_sword"):
		failures.append("returned unequipped weapon should appear in shop sell stock")
	scene.free()


func _check_unequip_ui_entries(failures: Array[String]) -> void:
	var scene = BattleSceneScript.new()
	scene._create_hud()
	scene.state.load_battle("res://data/scenarios/001_yellow_turbans.json")

	var selected: Dictionary = scene.state.get_unit("cao_cao")
	scene._rebuild_equip_menu(selected)
	if not _container_has_button_text(scene.equip_list, "Unequip Weapon:"):
		failures.append("battle equipment menu should include an Unequip Weapon button")

	scene.armory_unit_id = "cao_cao"
	scene._rebuild_armory_menu()
	if not _container_has_button_text(scene.armory_list, "Unequip Weapon:"):
		failures.append("pre-battle Armory should include an Unequip Weapon button")

	scene.free()


func _container_has_button_text(container: Node, fragment: String) -> bool:
	for child in container.get_children():
		if child is Button and str(child.text).contains(fragment):
			return true
	return false


func _loaded_state(failures: Array[String], label: String):
	var state = BattleStateScript.new()
	if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
		failures.append("%s could not load opening scenario" % label)
		return null
	return state
