-- lx-bundle:true

-- This file is part of Lx, created by Liqube <www.liqube.com>
-- Copyright Liqube Audio. All rights reserved.
-- Lx Interim License. See LICENSE for details.

local tests = {}

--- Return whether one test filename is an on-demand benchmark
-- @param name string test filename
-- @return boolean is_benchmark
local function is_benchmark_file(name)
	return name:match("_benchmark%.lua$") ~= nil
end

--- Returns sorted test file paths, optionally limited to one exact unit
-- @param requested_unit string|nil basename with or without '.lua'
-- @return table files
local function collect_test_files(requested_unit)
	local files = {}
	local seen = {}
	local roots = {}
	local requested_name = type(requested_unit) == "string" and requested_unit:gsub("%.lua$", "") or nil
	if TESTS_DIR then
		roots[#roots + 1] = TESTS_DIR
	end
	for r = 1, #roots do
		local root = roots[r]
		local names = system.list_dir(root) or {}
		for i = 1, #names do
			local name = names[i]
			local unit_name = name:gsub("%.lua$", "")
			local selected = requested_name == nil or unit_name == requested_name
			local included_by_default = requested_name ~= nil or not is_benchmark_file(name)

			if name:match("%.lua$") and selected and included_by_default then
				local path = root .. PATHSEP .. name
				if not seen[path] then
					seen[path] = true
					files[#files + 1] = path
				end
			end
		end
	end
	table.sort(files)
	return files
end

-- Derives unit label from test file path
local function unit_label_from_path(path)
	local name = (type(path) == "string" and path:match("([^/\\]+)$")) or "unit"
	return (name:gsub("%.lua$", ""))
end

--- Execute test files and report a compact aggregate summary
-- @param core table core API
-- @param config table|nil runtime configuration
-- @param opts table|nil runner options
-- @return boolean passed true when every test unit succeeds
function tests.run_tests(core, config, opts)
	opts = opts or {}
	local force = opts.force == true
	local traceback = (_G.debug and _G.debug.traceback) or tostring -- Use global debug.traceback without shadowing module table
	local testmod = require("core.test")
	local files = collect_test_files(opts.unit)
	local unit_total = #files
	local unit_passed = 0
	local unit_failed = 0
	local unit_skipped = 0
	local test_total = 0
	local test_passed = 0
	local test_failed = 0
	local cli_failures = {}

	--- Retain one actionable failure for terminal output
	-- @param unit_name string test unit label
	-- @param message string failure name and reason
	local function record_cli_failure(unit_name, message)
		if opts.cli == true then
			cli_failures[#cli_failures + 1] = string.format(
				"[Test] FAIL <%s> %s",
				unit_name,
				tostring(message)
			)
		end
	end

	if not force then
		if config and config.debug_auto_run_tests == false then
			return true
		end
		if config and config.debug_auto_run_tests == true then
			core.log_quiet("[Debug] Auto-running tests on startup")
		end
	end

	if unit_total == 0 then
		if force then
			local message = opts.unit
				and string.format('No test unit named "%s" was found', tostring(opts.unit))
				or "No units found in <tests/*.lua>"
			core.error("[Debug] %s", message)
			if opts.cli == true then
				print("[Test] RESULT=FAIL units=0 tests=0")
			end
		end

		return force ~= true
	end

	for i = 1, unit_total do
		local path = files[i]
		local unit_name = unit_label_from_path(path)
		local unit_state = "ok"
		local unit_stats = nil
		local recorded_test_failures = 0
		if type(testmod._set_current_unit) == "function" then
			testmod._set_current_unit(path)
		end
		if type(testmod._take_finished_result) == "function" then
			testmod._take_finished_result()
		end
		local chunk, load_err = loadfile(path)
		if not chunk then
			unit_state = "fail"
			-- Show test file path with shared core log-path formatting
			core.error("[Debug] Error in test unit <%s>\n%s", unit_name, tostring(load_err))
			record_cli_failure(unit_name, "load error: " .. tostring(load_err))
		else
			local function run_unit()
				return chunk()
			end
			local ok, run_a, run_b = xpcall(run_unit, traceback)
			if type(testmod._take_finished_result) == "function" then
				unit_stats = testmod._take_finished_result()
			end
			if not ok then
				unit_state = "fail"
				core.log_quiet_level("error", "[Debug] Failed test unit <%s>\n%s", unit_name, tostring(run_a))
				record_cli_failure(unit_name, "crashed: " .. tostring(run_a))
			elseif run_a == true then
				unit_state = "ok"
				core.log_quiet_level("success", "[Debug] Passed test unit <%s>", unit_name)
			elseif run_a == false then
				unit_state = "fail"
				core.log_quiet_level(
					"error",
					'[Debug] Failed test unit <%s> "%s"',
					unit_name,
					tostring(run_b or "returned false")
				)
				if not unit_stats then
					record_cli_failure(unit_name, tostring(run_b or "returned false"))
				end
			elseif run_a == nil then
				unit_state = "skip"
				core.log_quiet_level("warning", "[Debug] Skipped test unit <%s>", unit_name)
			else
				unit_state = "fail"
				core.log_quiet_level(
					"error",
					'[Debug] Test unit <%s> returns invalid type "%s"',
					unit_name,
					type(run_a)
				)
				record_cli_failure(unit_name, 'invalid return type "' .. type(run_a) .. '"')
			end
		end

		if unit_stats then
			recorded_test_failures = tonumber(unit_stats.failed) or 0
			test_total = test_total + (unit_stats.total or 0)
			test_passed = test_passed + (unit_stats.passed or 0)
			test_failed = test_failed + recorded_test_failures
			if unit_state ~= "skip" then
				if unit_stats.ok == true then
					unit_state = "ok"
				elseif unit_stats.ok == false then
					unit_state = "fail"
					-- Also print per-test failure details when unit state is derived from finish() stats
					if type(unit_stats.failures) == "table" then
						for j = 1, #unit_stats.failures do
							local failure = tostring(unit_stats.failures[j])
							core.log_quiet_level(
								"error",
								"[Debug] <%s> %s",
								unit_name,
								failure
							)
							record_cli_failure(unit_name, failure)
						end
					end
				end
			end
		end

		if unit_state == "fail" and recorded_test_failures == 0 then
			test_total = test_total + 1
			test_failed = test_failed + 1
		end

		if unit_state == "ok" then
			unit_passed = unit_passed + 1
		elseif unit_state == "skip" then
			unit_skipped = unit_skipped + 1
		elseif unit_state == "fail" then
			unit_failed = unit_failed + 1
		end
	end

	if unit_failed > 0 then
		local run_msg = string.format(
			--      "Test units: %d (%d ok, %d fail) -> Tests: %d (%d ok, %d fail)",
			"Failed %d of %d tests in %d units",
			test_failed,
			test_total,
			unit_failed
		)
		core.error("[Debug] %s", run_msg)
		if opts.cli == true then
			for failure_index = 1, #cli_failures do
				print(cli_failures[failure_index])
			end

			print(string.format("[Test] RESULT=FAIL units=%d tests=%d failed=%d", unit_total, test_total, test_failed))
		end

		return false
	else
		local run_msg = string.format("Passed %d tests in %d units without errors", test_total, unit_total)
		core.success("[Debug] %s", run_msg)
		if opts.cli == true then
			print(string.format("[Test] RESULT=PASS units=%d tests=%d", unit_total, test_total))
		end

		return true
	end
end

return tests
