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_contains(
		failures,
		"001 HUD progress",
		_progress_text("res://data/scenarios/001_yellow_turbans.json", false),
		"Enemies defeated 0/"
	)
	_check_contains(
		failures,
		"001 commander defeat risk",
		_defeat_text("res://data/scenarios/001_yellow_turbans.json"),
		"Cao Cao safe"
	)
	_check_contains(
		failures,
		"001 turn pressure risk",
		_defeat_text("res://data/scenarios/001_yellow_turbans.json"),
		"Turns left 7"
	)
	_check_contains(
		failures,
		"002 gated victory progress",
		_progress_text("res://data/scenarios/002_sishui_gate.json", false),
		"Objective unlocks on Turn 2"
	)
	_check_contains(
		failures,
		"002 pending enemy progress",
		_progress_text("res://data/scenarios/002_sishui_gate.json", false),
		"Enemies defeated 0/"
	)
	_check_contains(
		failures,
		"007 destination progress",
		_progress_text("res://data/scenarios/007_xian_emperor_escort.json", false),
		"Destination (14, 5): not reached"
	)
	_check_contains(
		failures,
		"007 protected-unit risk progress",
		_defeat_text("res://data/scenarios/007_xian_emperor_escort.json"),
		"Imperial Envoy safe"
	)
	_check_contains(
		failures,
		"045 gated destination progress",
		_progress_text("res://data/scenarios/045_fancheng_relief.json", false),
		"Reach (10, 5): not reached"
	)
	_check_event_gated_objective_markers(failures)
	_check_event_gated_multi_cell_objective_markers(failures)
	_check_turn_gated_objective_does_not_create_marker(failures)
	_check_objective_update(failures)
	_check_objective_notice(failures)

	if failures.is_empty():
		print("objective progress smoke ok")
		quit(0)
		return

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


func _progress_text(path: String, include_defeat_risks: bool) -> String:
	var state = BattleStateScript.new()
	if not state.load_battle(path):
		return "<load failed: %s>" % path
	return state.get_objective_progress_text(include_defeat_risks)


func _defeat_text(path: String) -> String:
	var state = BattleStateScript.new()
	if not state.load_battle(path):
		return "<load failed: %s>" % path
	return state.get_defeat_progress_text()


func _check_objective_update(failures: Array[String]) -> void:
	var state = BattleStateScript.new()
	if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
		failures.append("objective update smoke could not load scenario")
		return

	var signal_values := []
	var logs := []
	state.objective_updated.connect(func(victory: String, defeat: String) -> void:
		signal_values.append({"victory": victory, "defeat": defeat})
	)
	state.log_added.connect(func(message: String) -> void:
		logs.append(message)
	)
	state._apply_objective_event({
		"victory": "Defeat the new vanguard.",
		"defeat": "Cao Cao is defeated."
	})
	if signal_values.size() != 1:
		failures.append("objective update signal expected 1 emission, got %d" % signal_values.size())
	else:
		var signal_value: Dictionary = signal_values[0]
		if str(signal_value.get("victory", "")) != "Defeat the new vanguard.":
			failures.append("objective update signal victory mismatch: %s" % str(signal_value.get("victory", "")))
		if str(signal_value.get("defeat", "")) != "Cao Cao is defeated.":
			failures.append("objective update signal defeat mismatch: %s" % str(signal_value.get("defeat", "")))
	_check_log_contains(failures, logs, "Objective updated: Defeat the new vanguard.")
	_check_log_contains(failures, logs, "Defeat condition updated: Cao Cao is defeated.")

	signal_values.clear()
	logs.clear()
	state._apply_objective_event({"defeat": "Supply train is lost."})
	if signal_values.size() != 1:
		failures.append("defeat-only objective signal expected 1 emission, got %d" % signal_values.size())
	else:
		var defeat_signal_value: Dictionary = signal_values[0]
		if not str(defeat_signal_value.get("victory", "")).is_empty():
			failures.append("defeat-only objective signal should not include victory: %s" % str(defeat_signal_value.get("victory", "")))
		if str(defeat_signal_value.get("defeat", "")) != "Supply train is lost.":
			failures.append("defeat-only objective signal mismatch: %s" % str(defeat_signal_value.get("defeat", "")))
	_check_log_contains(failures, logs, "Defeat condition updated: Supply train is lost.")


func _check_event_gated_objective_markers(failures: Array[String]) -> void:
	var state = BattleStateScript.new()
	if not state.load_battle("res://data/scenarios/013_wuchao_raid.json"):
		failures.append("event-gated objective marker smoke could not load Wuchao raid")
		return

	var depot_cell := Vector2i(14, 4)
	var initial_cells := state.get_objective_cells()
	if not initial_cells.has(depot_cell):
		failures.append("Wuchao depot marker missing before event opens victory condition: %s" % str(initial_cells))

	var cao_cao: Dictionary = state.get_unit("cao_cao_ch13")
	if cao_cao.is_empty():
		failures.append("event-gated objective marker smoke missing Cao Cao")
		return
	cao_cao["pos"] = depot_cell
	state._run_events("unit_reaches_tile", "player", 1, cao_cao)
	var opened_cells := state.get_objective_cells()
	if opened_cells.has(depot_cell):
		failures.append("Wuchao depot marker should clear after event opens defeat-all objective: %s" % str(opened_cells))


func _check_event_gated_multi_cell_objective_markers(failures: Array[String]) -> void:
	var state = BattleStateScript.new()
	if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
		failures.append("multi-cell event-gated objective marker smoke could not load scenario")
		return

	state.battle_conditions["victory"] = {
		"type": "all_units_defeated",
		"team": "enemy",
		"after_event": "multi_cell_gate"
	}
	state.battle_events.clear()
	state.battle_events.append({
		"id": "multi_cell_gate",
		"once": true,
		"when": {"type": "unit_reaches_tile", "team": "player", "cells": [[3, 2], [3, 3]]},
		"actions": [{"type": "log", "text": "Gate opened."}]
	})

	var marker_cells := state.get_objective_cells()
	if not marker_cells.has(Vector2i(3, 2)) or not marker_cells.has(Vector2i(3, 3)):
		failures.append("multi-cell event-gated marker should include every event cell: %s" % str(marker_cells))


func _check_turn_gated_objective_does_not_create_marker(failures: Array[String]) -> void:
	var state = BattleStateScript.new()
	if not state.load_battle("res://data/scenarios/001_yellow_turbans.json"):
		failures.append("turn-gated objective marker smoke could not load scenario")
		return

	state.battle_conditions["victory"] = {
		"type": "all_units_defeated",
		"team": "enemy",
		"after_event": "turn_gate"
	}
	state.battle_events.clear()
	state.battle_events.append({
		"id": "turn_gate",
		"once": true,
		"when": {"type": "turn_start", "team": "player", "turn": 2},
		"actions": [{"type": "log", "text": "Turn gate opened."}]
	})

	var marker_cells := state.get_objective_cells()
	if not marker_cells.is_empty():
		failures.append("turn-gated objective should not create map markers: %s" % str(marker_cells))


func _check_log_contains(failures: Array[String], logs: Array, expected: String) -> void:
	for log_entry in logs:
		if str(log_entry) == expected:
			return
	failures.append("expected log `%s` in `%s`" % [expected, logs])


func _check_objective_notice(failures: Array[String]) -> void:
	var scene = BattleSceneScript.new()
	scene._create_hud()
	scene._on_objective_updated("Defeat the new vanguard.", "")
	if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible:
		failures.append("objective notice panel should be visible after objective update")
	elif not scene.objective_notice_label.text.contains("Defeat the new vanguard."):
		failures.append("objective notice label missing updated objective: %s" % scene.objective_notice_label.text)
	scene._on_objective_updated("", "Supply train is lost.")
	if scene.objective_notice_panel == null or not scene.objective_notice_panel.visible:
		failures.append("objective notice panel should be visible after defeat update")
	elif not scene.objective_notice_label.text.contains("Supply train is lost."):
		failures.append("objective notice label missing updated defeat condition: %s" % scene.objective_notice_label.text)
	if scene.objective_notice_timer <= 0.0:
		failures.append("objective notice timer should be active")
	scene.free()


func _check_contains(failures: Array[String], label: String, text: String, expected: String) -> void:
	if text.contains(expected):
		return
	failures.append("%s expected `%s` in `%s`" % [label, expected, text])
