local DEFAULT_TIMEOUT_SECONDS = 3

local function createLibTest(args)
	local api = {}
	local tests = {}
	local pretty = false
	local verbose = false
	local reportPath = nil
	local printMarker = true
	local timeoutSeconds = DEFAULT_TIMEOUT_SECONDS

	local i = 1
	while i <= #(args or {}) do
		local arg = args[i]
		if arg == "--verbose" then
			pretty = true
			verbose = true
		elseif arg == "--pretty" then
			pretty = true
		elseif arg == "--report" then
			reportPath = args[i + 1]
			i = i + 1
		elseif arg == "--no-marker" then
			printMarker = false
		elseif arg == "--timeout" then
			timeoutSeconds = tonumber(args[i + 1])
			i = i + 1
		elseif arg == "--no-timeout" then
			timeoutSeconds = nil
		end
		i = i + 1
	end

	local function fail(message)
		error(message, 2)
	end

	local function writeReport(line)
		if not reportPath then
			return
		end

		local file = fs.open(reportPath, "a")
		if file then
			file.writeLine(line)
			file.close()
		end
	end

	function api.log(message)
		if verbose then
			writeReport("LOG " .. tostring(message))
		end
	end

	function api.test(name, fn)
		assert(type(name) == "string", "bad argument #1 (string expected)")
		assert(type(fn) == "function", "bad argument #2 (function expected)")

		tests[#tests + 1] = { name = name, fn = fn }
	end

	function api.assertEquals(actual, expected, message)
		if actual ~= expected then
			fail(
				(message or "assertEquals failed")
					.. ": expected "
					.. tostring(expected)
					.. ", got "
					.. tostring(actual)
			)
		end
	end

	function api.assertTrue(value, message)
		if not value then
			fail(message or "assertTrue failed")
		end
	end

	function api.assertErrors(fn, expected)
		assert(type(fn) == "function", "bad argument #1 (function expected)")

		local ok, err = pcall(fn)
		if ok then
			fail("expected error")
		end
		if expected and not string.find(tostring(err), expected, 1, true) then
			fail("expected error containing " .. expected .. ", got " .. tostring(err))
		end
	end

	local function runCase(fn)
		if not timeoutSeconds then
			return pcall(fn)
		end

		local ok, err
		local finished = false
		local function runner()
			ok, err = pcall(fn)
			finished = true
		end
		local function timer()
			sleep(timeoutSeconds)
		end

		parallel.waitForAny(runner, timer)
		if not finished then
			return false, "libtest timeout after " .. timeoutSeconds .. "s", true
		end
		return ok, err
	end

	function api.run()
		for _, t in ipairs(tests) do
			api.log("RUN " .. t.name)
			local ok, err, timedOut = runCase(t.fn)
			if not ok then
				if timedOut then
					api.log("TIMEOUT " .. t.name .. " after " .. timeoutSeconds .. "s (libtest)")
				end
				writeReport("KO " .. t.name .. ": " .. tostring(err))
				if not pretty then
					print("FAIL " .. t.name .. ": " .. tostring(err))
				end
				error(err, 0)
			end
			writeReport("OK " .. t.name)
		end

		if printMarker then
			print("__TRAPOS_TEST_OK__")
		end
		return true
	end

	return api
end

return createLibTest
