struct CliHttpResponse
{
	u64 status = 0;
	String raw;
	String headers;
	String body;
};

u64 cli_tests_passed = 0;
u64 cli_tests_failed = 0;
u64 cli_tests_skipped = 0;
StringList cli_tests_failures;

bool cli_contains(String haystack, String needle)
{
	return(haystack.find(needle) != String::npos);
}

String cli_truncate(String value, u64 max_len = 240)
{
	if(value.length() <= max_len)
		return(value);
	return(value.substr(0, max_len) + "...");
}

String cli_tail(String value, u64 max_len = 500)
{
	if(value.length() <= max_len)
		return(value);
	return("..." + value.substr(value.length() - max_len));
}

CliHttpResponse cli_http_request(String host, u16 port, String path, String extra_headers = "")
{
	CliHttpResponse res;
	u64 fd = socket_connect(host, port);
	if(fd == 0)
	{
		res.raw = "socket_connect failed";
		return(res);
	}
	String host_header = host;
	if(host == "127.0.0.1" && port == 80)
		host_header = "uce.openfu.com";
	else if(host == "127.0.0.1" && port == 8080)
		host_header = "localhost";
	String request = "GET " + path + " HTTP/1.0\r\nHost: " + host_header + "\r\nConnection: close\r\n" + extra_headers + "\r\n";
	socket_write(fd, request);
	for(u64 i = 0; i < 16; i++)
	{
		String chunk = socket_read(fd, 65536, 30);
		if(chunk == "")
			break;
		res.raw += chunk;
	}
	socket_close(fd);

	u64 line_end = res.raw.find("\r\n");
	if(line_end != String::npos)
	{
		StringList parts = split(res.raw.substr(0, line_end), " ");
		if(parts.size() >= 2)
			res.status = int_val(parts[1]);
	}
	u64 split_at = res.raw.find("\r\n\r\n");
	if(split_at != String::npos)
	{
		res.headers = res.raw.substr(0, split_at + 4);
		res.body = res.raw.substr(split_at + 4);
	}
	else
	{
		res.headers = res.raw;
		res.body = "";
	}
	return(res);
}

CliHttpResponse cli_frontend(String path, String extra_headers = "")
{
	return(cli_http_request("127.0.0.1", 80, path, extra_headers));
}

CliHttpResponse cli_direct_http(String path, String extra_headers = "")
{
	return(cli_http_request("127.0.0.1", 8080, path, extra_headers));
}

void cli_test_case(String name, bool ok, String summary)
{
	if(ok)
	{
		cli_tests_passed++;
		print("[PASS] ", name, " - ", summary, "\n");
	}
	else
	{
		cli_tests_failed++;
		cli_tests_failures.push_back(name + ": " + summary);
		print("[FAIL] ", name, " - ", summary, "\n");
	}
}

void cli_test_skip(String name, String summary)
{
	cli_tests_skipped++;
	print("[SKIP] ", name, " - ", summary, "\n");
}

bool cli_expect_http_once(CliHttpResponse& res, String path, u64 expected_status, String marker, StringList error_markers, String& summary)
{
	bool ok = res.status == expected_status;
	summary = "HTTP " + std::to_string(res.status) + " for " + path;
	if(marker != "" && !cli_contains(res.body, marker))
	{
		ok = false;
		summary += "; missing marker " + marker;
	}
	String lower = to_lower(res.body);
	for(String marker_text : error_markers)
	{
		if(cli_contains(lower, marker_text))
		{
			ok = false;
			summary += "; body contained " + marker_text;
		}
	}
	return(ok);
}

bool cli_expect_http(String name, String path, u64 expected_status, String marker, StringList error_markers = {})
{
	CliHttpResponse res = cli_frontend(path);
	String summary;
	bool ok = cli_expect_http_once(res, path, expected_status, marker, error_markers, summary);
	if(!ok && expected_status != 500)
	{
		usleep(500000);
		res = cli_frontend(path);
		ok = cli_expect_http_once(res, path, expected_status, marker, error_markers, summary);
	}
	cli_test_case(name, ok, ok ? summary : summary + "; body=" + cli_truncate(res.body));
	return(ok);
}

StringList cli_demo_error_markers()
{
	StringList markers;
	markers.push_back(String("uce compile ") + "error");
	markers.push_back(String("fatal signal during ") + "request");
	markers.push_back(String("uncaught exception during ") + "request");
	return(markers);
}

void cli_run_demo_smoke()
{
	StringList files = split("call_file.uce\ncall_file_funcs.uce\ncollections.uce\ncomponents.uce\ncookie.uce\ndvalue.uce\nempty.uce\nerror-reporting.uce\nfile_append.uce\nfileio.uce\nheader.uce\nhello.uce\nindex.uce\njson.uce\nmarkdown.uce\nmemcached.uce\nmysql.uce\nonce-init.uce\nparse_time.uce\npost-multipart.uce\npost.uce\npreprocessor-comments.uce\nrandom.uce\nregex.uce\nscript.uce\nsession.uce\nsharedunit.uce\nshell.uce\nsqlite.uce\nstr_replace.uce\nstring.uce\ntask-status.uce\ntask.uce\ntask_repeat.uce\nunit-browser.uce\nunit-info.uce\nuri.uce\nutf8.uce\nwebsockets.ws.uce\nworking-dir.uce\nxml.uce\nyaml.uce\nzip.uce", "\n");
	for(String file : files)
		cli_expect_http("uce_demo_smoke:demo page " + file, "/demo/" + file, 200, "", file == "unit-browser.uce" ? StringList() : cli_demo_error_markers());
}

bool cli_doc_source_has_example(String source)
{
	for(String line : split(source, "\n"))
		if(trim(line) == ":example")
			return(true);
	return(false);
}

// A real example must exercise the documented API and print a derived result.
// These placeholder shapes (auto-generated stubs) are forbidden by the gate.
bool cli_doc_example_is_placeholder(String source)
{
	bool in_example = false;
	StringList body;
	for(String line : split(source, "\n"))
	{
		String t = trim(line);
		if(!in_example)
		{
			if(t == ":example")
				in_example = true;
			continue;
		}
		if(t.length() > 0 && t.substr(0, 1) == ":")
			break;
		body.push_back(line);
	}
	String joined = join(body, "\n");
	if(cli_contains(joined, " example\\n\");"))
		return(true);
	if(cli_contains(joined, "is resource-bound; configure"))
		return(true);
	if(cli_contains(joined, "documents a UCE concept"))
		return(true);
	return(false);
}

void cli_run_doc_pages_gate()
{
	StringList error_markers;
	error_markers.push_back("doc example error");
	error_markers.push_back(String("uce compile ") + "error");
	error_markers.push_back(String("wasm runtime error during ") + "request");
	error_markers.push_back(String("fatal signal during ") + "request");
	error_markers.push_back(String("uncaught exception during ") + "request");
	error_markers.push_back("timed out acquiring compile lock");

	for(String file_name : ls("../doc/pages/"))
	{
		String source = file_get_contents("../doc/pages/" + file_name);
		String page = nibble(file_name, ".");
		if(page == "")
			continue;
		bool has_example = cli_doc_source_has_example(source);
		String path = "/doc/index.uce?p=" + uri_encode(page);
		CliHttpResponse res = cli_frontend(path);
		String summary;
		bool ok = cli_expect_http_once(res, path, 200, "doc-detail", error_markers, summary);
		String lower = to_lower(res.body);
		if(has_example)
		{
			if(!cli_contains(res.body, "class=\"example-output\"") || !cli_contains(res.body, "example-output-label\">Output"))
			{
				ok = false;
				summary += "; missing example Output block";
			}
			if(cli_contains(lower, "doc example error"))
			{
				ok = false;
				summary += "; example reported an error";
			}
			if(cli_doc_example_is_placeholder(source))
			{
				ok = false;
				summary += "; placeholder example (must exercise the API)";
			}
		}
		cli_test_case("uce_doc_gate:doc page " + page, ok, ok ? summary : summary + "; body=" + cli_truncate(res.body));
	}
}

void cli_run_http_smoke()
{
	cli_expect_http("uce_http_smoke:doc index", "/doc/index.uce", 200, "<html>");
	// Regression guard: the index enumerates pages via ls("pages/"). When that
	// returned empty (wasm ls() was a stub), the page still rendered <html> but
	// listed nothing — so assert the function grid is actually populated.
	cli_expect_http("uce_http_smoke:doc index lists functions", "/doc/index.uce", 200, "func-item");
	cli_expect_http("uce_http_smoke:doc singlepage", "/doc/singlepage.uce", 200, "<html>");
	cli_expect_http("uce_http_smoke:doc component page", "/doc/index.uce?p=component", 200, "component()");
	cli_expect_http("uce_http_smoke:doc regex page", "/doc/index.uce?p=regex_search", 200, "regex_search");
	cli_expect_http("uce_http_smoke:doc xml page", "/doc/index.uce?p=xml_encode", 200, "xml_encode");
	cli_expect_http("uce_http_smoke:doc yaml page", "/doc/index.uce?p=yaml_encode", 200, "yaml_encode");
	cli_expect_http("uce_http_smoke:doc relative time page", "/doc/index.uce?p=time_format_relative", 200, "time_format_relative");
	cli_expect_http("uce_http_smoke:starter home", "/examples/uce-starter/", 200, "Stunning Apps");
	cli_expect_http("uce_http_smoke:starter dashboard", "/examples/uce-starter/?dashboard", 200, "Dashboard");
	cli_expect_http("uce_http_smoke:starter dashboard ONCE assets reach head", "/examples/uce-starter/?dashboard", 200, "views/dashboard.css");
	cli_expect_http("uce_http_smoke:starter workspace nested route", "/examples/uce-starter/?workspace/projects", 200, "Workspace");
	cli_expect_http("uce_http_smoke:starter workspace ONCE assets reach head", "/examples/uce-starter/?workspace/projects", 200, "css/workspace.css");
	cli_expect_http("uce_http_smoke:starter ajax section", "/examples/uce-starter/?page2-section1", 200, "UCE starter AJAX fragment response");
	cli_expect_http("uce_http_smoke:starter route traversal blocked", "/examples/uce-starter/?../../../demo/index", 404, "The requested page does not exist.");
	// Configurable error page (page_runtime_error): a runtime fault must render the
	// developer-defined error-page unit with the real error info marshalled across
	// the membrane, at the error status. Asserts HTTP 500 AND that error["request_uri"]
	// reached the page (the "mode=trap" query is only shown if error_info crossed).
	cli_expect_http("uce_http_smoke:configurable error page 500 + error info", "/demo/error-reporting.uce?mode=trap", 500, "mode=trap");
}

bool cli_site_body_reports_failure(String lower)
{
	return(cli_contains(lower, ">failed 1") || cli_contains(lower, ">failed 2") || cli_contains(lower, ">failed 3") || cli_contains(lower, ">failed 4") || cli_contains(lower, ">failed 5") || cli_contains(lower, ">failed 6") || cli_contains(lower, ">failed 7") || cli_contains(lower, ">failed 8") || cli_contains(lower, ">failed 9"));
}

void cli_run_site_suite(bool skip_local_service_pages = false)
{
	DValue manifest;
	for(String line : split(file_get_contents("manifest.txt"), "\n"))
	{
		line = trim(line);
		if(line == "" || line[0] == '#')
			continue;
		StringList parts = split(line, "|");
		if(parts.size() < 7 || trim(parts[5]) != "1")
			continue;
		String file = trim(parts[0]);
		if(skip_local_service_pages && (file == "io.uce" || file == "services.uce"))
		{
			cli_test_skip(String("uce_site_suite:site tests ") + (file == "io.uce" ? "filesystem" : "services"), "skipped local nested self-HTTP service probe pages for this environment");
			continue;
		}
		String title = trim(parts[1]);
		String expected = first(trim(parts[4]), title);
		CliHttpResponse res;
		String lower;
		bool ok = false;
		for(u64 attempt = 0; attempt < 3; attempt++)
		{
			if(attempt > 0)
				usleep(1000000);
			res = cli_frontend("/tests/" + file);
			lower = to_lower(res.body);
			ok = res.status == 200 && cli_contains(res.body, expected);
			for(String marker : { "compile error", "runtime error", "timed out acquiring compile lock", "near line" })
				if(cli_contains(lower, marker))
					ok = false;
			if(cli_contains(lower, "<div class=\"tests-summary\">") && !cli_contains(lower, ">failed 0</span>") && cli_site_body_reports_failure(lower))
				ok = false;
			if(ok)
				break;
		}
		String name = "uce_site_suite:site tests " + (file == "index.uce" ? "index" : file == "io.uce" ? "filesystem" : file == "websockets.ws.uce" ? "websockets page" : replace(replace(replace(file, ".uce", ""), ".ws", ""), "_", " "));
		String fail_detail = "; status=" + std::to_string(res.status) + "; expected_marker=" + (cli_contains(res.body, expected) ? "yes" : "no") + "; summary=" + (cli_contains(lower, "<div class=\"tests-summary\">") ? "yes" : "no") + "; failed_zero=" + (cli_contains(lower, ">failed 0</span>") ? "yes" : "no") + "; reports_failure=" + (cli_site_body_reports_failure(lower) ? "yes" : "no") + "; body=" + cli_truncate(res.body) + "; tail=" + cli_tail(res.body);
		cli_test_case(name, ok, (ok ? "HTTP 200 with suite page marker and no failed cases for /tests/" : "site suite failed for /tests/") + file + (ok ? "" : fail_detail));
	}
}

void cli_run_security_smoke()
{
	CliHttpResponse dotdot = cli_direct_http("/../site/demo/hello.uce");
	cli_test_case("uce_security_smoke:direct HTTP rejects dot-dot script traversal", dotdot.status != 200 && !cli_contains(dotdot.body, "hello world"), "direct HTTP dot-dot traversal returned HTTP " + std::to_string(dotdot.status));

	CliHttpResponse spoof = cli_direct_http("/no-such-script.uce", "Script-Filename: /Code/uce.openfu.com/uce/site/demo/hello.uce\r\n");
	cli_test_case("uce_security_smoke:direct HTTP ignores Script-Filename header", spoof.status != 200 && !cli_contains(spoof.body, "hello world"), "direct HTTP Script-Filename override returned HTTP " + std::to_string(spoof.status));

	CliHttpResponse sanitizer = cli_frontend("/tests/security_headers.uce");
	String headers_lower = to_lower(sanitizer.headers);
	bool injected = false;
	for(String header : { "x-uce-injected", "x-uce-injected-name", "x-uce-cookie-injected", "x-uce-redirect-injected", "x-uce-status-injected" })
		if(cli_contains(headers_lower, "\r\n" + to_lower(header) + ":"))
			injected = true;
	CliHttpResponse direct_sanitizer = cli_direct_http("/site/tests/security_headers.uce");
	bool direct_ok = direct_sanitizer.status < 500 || cli_contains(direct_sanitizer.body, "security header sanitizer test");
	cli_test_case("uce_security_smoke:response headers sanitize CRLF", !injected && direct_ok, "CRLF response header injection sanitized");

	String attacker = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
	CliHttpResponse session = cli_frontend("/tests/http.uce", "Cookie: uce-site-tests=" + attacker + "\r\n");
	String session_headers = session.headers;
	bool adopted = cli_contains(session_headers, "Set-Cookie: uce-site-tests=" + attacker);
	bool hardened = cli_contains(session_headers, "Set-Cookie: uce-site-tests=") && cli_contains(session_headers, "HttpOnly") && cli_contains(session_headers, "SameSite=Lax");
	cli_test_case("uce_security_smoke:unknown session ids are not adopted", !adopted && hardened, "unknown caller-supplied session id was replaced");
}

void cli_run_starter_parity()
{
	StringList markers = {
		"landing\t\tHome | UCE Starter",
		"dashboard\t?dashboard\tDashboard | UCE Starter",
		"gauges\t?gauges\tGauges | UCE Starter",
		"features\t?features\tFeatures | UCE Starter",
		"components\t?page1\tComponents | UCE Starter",
		"workspace\t?workspace\tWorkspace | UCE Starter"
	};
	for(String row : markers)
	{
		StringList parts = split(row, "\t");
		String name = parts[0];
		String query = parts[1];
		String title = parts[2];
		cli_expect_http("uce_starter_parity:starter view " + name, "/examples/uce-starter/index.uce" + query, 200, "<title>" + title + "</title>", cli_demo_error_markers());
	}
	cli_expect_http("uce_starter_parity:starter unknown route 404", "/examples/uce-starter/index.uce?does-not-exist", 404, "<title>404 Not Found | UCE Starter</title>", cli_demo_error_markers());
}

void cli_run_task_lifetime_smoke()
{
	String late_file = "/tmp/uce-site-tests-late-task.txt";
	file_unlink(late_file);
	CliHttpResponse res = cli_frontend("/tests/tasks.uce?mode=late");
	usleep(3000000);
	String observed = file_exists(late_file) ? file_get_contents(late_file) : "";
	cli_test_case("uce_task_lifetime:delayed callback after request return", res.status == 200 && cli_contains(res.body, "late task scheduled") && cli_contains(observed, "late callback ran"), "status=" + std::to_string(res.status) + " observed=" + cli_truncate(observed));
}

void cli_run_tcp_smoke()
{
	u64 fd80 = socket_connect("127.0.0.1", 80);
	cli_test_case("uce_tcp_smoke:frontend port 80", fd80 != 0, "TCP connect " + String(fd80 != 0 ? "succeeded" : "failed") + " on port 80");
	if(fd80 != 0)
		socket_close(fd80);
	u64 fd8080 = socket_connect("127.0.0.1", 8080);
	cli_test_case("uce_tcp_smoke:http websocket port 8080", fd8080 != 0, "TCP connect " + String(fd8080 != 0 ? "succeeded" : "failed") + " on port 8080");
	if(fd8080 != 0)
		socket_close(fd8080);
}

void cli_run_wasm_kill(bool include_kill)
{
	if(!include_kill)
		return;
	for(String row : { "wasm kill trap|/tests/wasm-kill/oob.uce|unreachable", "wasm kill loop|/tests/wasm-kill/loop.uce|interrupt", "wasm kill recurse|/tests/wasm-kill/recurse.uce|wasm_kill_recurse" })
	{
		StringList parts = split(row, "|");
		CliHttpResponse res = cli_frontend(parts[1]);
		CliHttpResponse health = cli_frontend("/demo/hello.uce");
		bool ok = res.status == 500 && cli_contains(res.body, "wasm runtime error during request") && cli_contains(res.body, parts[2]) && health.status == 200 && cli_contains(health.body, "hello world");
		cli_test_case("uce_wasm_kill:" + parts[0], ok, ok ? "clean wasm trap page and post-trap health check for " + parts[1] : "kill check failed; status=" + std::to_string(res.status) + " health=" + std::to_string(health.status));
	}
}

void cli_print_list(bool include_kill)
{
	print("UCE CLI test groups:\n");
	print("  demo, http, site, security, starter, tcp");
	if(include_kill)
		print(", wasm-kill");
	print("\n");
}

CLI(Request& context)
{
	context.header["Content-Type"] = "text/plain; charset=utf-8";
	DValue input = cli_input(context);
	String action = first(input["action"].to_string(), "run");
	bool include_kill = input["include_kill"].to_bool() || input["include_wasm_kill"].to_bool();
	if(action == "list")
	{
		cli_print_list(include_kill);
		return;
	}

	print("Running UCE CLI tests\n");
	f64 group_start = time_precise();
	cli_run_demo_smoke();
	print("[GROUP] demo ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
	group_start = time_precise();
	cli_run_http_smoke();
	print("[GROUP] http ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
	group_start = time_precise();
	cli_run_site_suite(input["skip_local_service_pages"].to_bool());
	print("[GROUP] site ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
	group_start = time_precise();
	cli_run_doc_pages_gate();
	print("[GROUP] doc-gate ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
	group_start = time_precise();
	cli_run_security_smoke();
	print("[GROUP] security ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
	group_start = time_precise();
	cli_run_task_lifetime_smoke();
	print("[GROUP] task-lifetime ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
	group_start = time_precise();
	cli_run_starter_parity();
	print("[GROUP] starter ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
	group_start = time_precise();
	cli_run_tcp_smoke();
	String repo_root = process_start_directory();
	String cli_arg_out = shell_exec("cd " + shell_escape(repo_root) + " && bash scripts/uce-cli /tests/cli.uce action=arg message=hello-cli 2>&1");
	cli_test_case("uce_cli_fixture:cli_arg", contains(cli_arg_out, "hello-cli") && contains(cli_arg_out, "fallback"), cli_truncate(cli_arg_out));
	print("[GROUP] tcp ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
	group_start = time_precise();
	cli_run_wasm_kill(include_kill);
	print("[GROUP] wasm-kill ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
	print("\nSummary: ", std::to_string(cli_tests_passed), " passed, ", std::to_string(cli_tests_failed), " failed, ", std::to_string(cli_tests_skipped), " skipped\n");
	if(cli_tests_failed != 0)
	{
		context.set_status(500, "CLI Tests Failed");
		print("Failures:\n");
		for(String failure : cli_tests_failures)
			print("- ", failure, "\n");
	}
}

RENDER(Request& context)
{
	context.set_status(404, "Not Found");
	context.header["Content-Type"] = "text/plain; charset=utf-8";
	print("Invoke this test runner through the UCE CLI socket.\n");
}
