// #region RoutesLinkIntegrityTest [C:3] [TYPE Module] [SEMANTICS test,routes,navigation,integrity]
// @BRIEF Verify that every ROUTES builder produces a valid path matching an existing route file,
//   and detect raw href/goto strings that bypass the ROUTES registry.
// @RELATION BINDS_TO -> [RoutesRegistry]
// @TEST_INVARIANT: all-routes-valid -> VERIFIED_BY: [test_all_route_builders_produce_valid_paths]
// @TEST_INVARIANT: no-raw-goto-href -> VERIFIED_BY: [test_no_raw_internal_href_goto_strings]
import { describe, it, expect } from "vitest";
import fs from "node:fs";
import path from "node:path";
import { ROUTES } from "../routes.js";

// ═══════════════════════════════════════════════════════════════════
// 1. Route registry integrity — every ROUTES builder -> real file
// ═══════════════════════════════════════════════════════════════════

/**
 * All known route patterns from the filesystem (src/routes/).
 * Each entry: pattern string + regex that matches concrete URLs.
 */
interface RouteEntry {
	pattern: string; // e.g. "dashboards/[id]/"
	regex: RegExp;   // matches concrete URL pathname
}

const ROUTES_DIR = path.resolve(process.cwd(), "src/routes");

/** Discover all route patterns from the filesystem. */
function discoverRoutePatterns(): RouteEntry[] {
	const files = fs.readdirSync(ROUTES_DIR, { recursive: true, withFileTypes: true });
	const pageFiles = new Set<string>();

	for (const entry of files) {
		if (!entry.isFile()) continue;
		const relativePath = path.relative(ROUTES_DIR, path.join(entry.parentPath, entry.name));
		// Only +page.svelte and +page.ts define routes; +layout doesn't create a route entry
		if (relativePath.endsWith("+page.svelte") || relativePath.endsWith("+page.ts")) {
			// Convert to route pattern: strip +page.*, keep directory
			const dir = path.dirname(relativePath);
			// Root page: if dir is "." the pattern is "/"
			const pattern = dir === "." ? "/" : `/${dir}/`;
			pageFiles.add(pattern);
		}
	}

	return Array.from(pageFiles).map((pattern) => {
		// Convert SvelteKit param syntax [id] to regex wildcard [^/]+
		const regexStr = pattern
			.split("/")
			.map((segment) => {
				if (segment.startsWith("[") && segment.endsWith("]")) return "[^/]+";
				return segment;
			})
			.join("/")
			// Allow optional trailing slash
			.replace(/\/$/, "/?")
			// Anchor at start
			.replace(/^\//, "^/");
		return {
			pattern,
			regex: new RegExp(regexStr + "$"),
		};
	});
}

const routePatterns = discoverRoutePatterns();

/**
 * Sample values for route params — used to generate concrete URLs.
 * Must be chosen to match the param patterns in route files.
 */
const SAMPLE = {
	id: "42",
	envId: "ss-dev",
	dashboardId: "42",
	policyId: "550e8400-e29b-41d4-a716-446655440000",
	runId: "550e8400-e29b-41d4-a716-446655440001",
	taskId: "550e8400-e29b-41d4-a716-446655440002",
	searchQuery: "test-dashboard",
	datasetId: "101",
	sessionId: "session-abc-123",
};

type RouteBuilder = () => string;

/** Collect all route builder functions recursively from the ROUTES object. */
function collectBuilders(obj: unknown, prefix = ""): Array<{ name: string; fn: RouteBuilder; params: string[] }> {
	const builders: Array<{ name: string; fn: RouteBuilder; params: string[] }> = [];

	if (typeof obj === "function") {
		builders.push({ name: prefix, fn: obj as RouteBuilder, params: [] });
	} else if (obj !== null && typeof obj === "object") {
		for (const [key, value] of Object.entries(obj)) {
			const childBuilders = collectBuilders(value, prefix ? `${prefix}.${key}` : key);
			builders.push(...childBuilders);
		}
	}
	return builders;
}

describe("ROUTES link integrity", () => {
	const builders = collectBuilders(ROUTES);

	it.each(builders)("$name produces a path that matches a route file", ({ name, fn, params }) => {
		// Call the builder with sample params
		let url: string;
		try {
			url = (fn as (...args: unknown[]) => string)(...Object.values(SAMPLE).slice(0, params.length || 1));
		} catch {
			// Try with no args
			url = (fn as () => string)();
		}

		expect(typeof url).toBe("string");
		expect(url).toMatch(/^\//);

		// Extract pathname (strip query string and hash fragment)
		const pathname = url.split("?")[0].split("#")[0];
		// Remove trailing slash for matching
		const normalizedPath = pathname.endsWith("/") && pathname !== "/" ? pathname.slice(0, -1) : pathname;

		// Find matching route pattern
		const match = routePatterns.some((rp) => rp.regex.test(pathname));

		expect(match).toBe(true);
	});

	it("every route file has at least one matching ROUTES builder", () => {
		const builderUrls = builders.map((b) => {
			try {
				return (b.fn as (...args: unknown[]) => string)(...Object.values(SAMPLE).slice(0, b.params.length || 1));
			} catch {
				return (b.fn as () => string)();
			}
		});

		for (const pattern of routePatterns) {
			if (pattern.pattern === "/") continue; // root is covered by ROUTES.home()
			const matched = builderUrls.some((url) => pattern.regex.test(url.split("?")[0]));
			if (!matched) {
				// Route pattern has no matching ROUTES builder — may be OK if unreferenced,
				// but warn. These patterns exist as files:
				const filePath = path.join(ROUTES_DIR, pattern.pattern.slice(1));
				const resolved = filePath.endsWith("/") ? filePath.slice(0, -1) : filePath;
				// Check if the page file actually exists
				expect(fs.existsSync(path.join(resolved, "+page.svelte")) || fs.existsSync(path.join(resolved, "+page.ts")))
					.toBe(true);
			}
		}
	});
});

// ═══════════════════════════════════════════════════════════════════
// 2. Detect raw internal href / goto strings bypassing ROUTES
// ═══════════════════════════════════════════════════════════════════

/** Internal route prefixes we care about (not external URLs like /api/ or /auth/). */
const INTERNAL_PREFIXES = [
	"/dashboards", "/datasets", "/migration", "/git", "/translate",
	"/validation", "/reports", "/settings", "/admin", "/storage",
	"/tools", "/profile", "/maintenance", "/login",
];

describe("raw href/goto strings", () => {
	const SRC_DIR = path.resolve(process.cwd(), "src");
	const extensions = [".svelte", ".ts"];

	function collectSourceFiles(): string[] {
		const files: string[] = [];
		function walk(dir: string) {
			for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
				const fullPath = path.join(dir, entry.name);
				if (entry.isDirectory() && !entry.name.startsWith("__tests__") && !entry.name.startsWith("node_modules")) {
					walk(fullPath);
				} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
					files.push(fullPath);
				}
			}
		}
		walk(SRC_DIR);
		return files;
	}

	const sourceFiles = collectSourceFiles();

	it("no files contain raw href pointing to internal routes (use ROUTES instead)", () => {
		const violations: Array<{ file: string; line: number; match: string }> = [];

		for (const file of sourceFiles) {
			// Skip files that define the ROUTES registry itself
			if (file.endsWith("routes.ts") || file.endsWith("routes-link-integrity.test.ts")) continue;
			// Skip test files and node_modules
			if (file.includes("__tests__") || file.includes("node_modules")) continue;

			const content = fs.readFileSync(file, "utf-8");
			const lines = content.split("\n");

			for (let i = 0; i < lines.length; i++) {
				const line = lines[i];
				// Skip lines that use ROUTES
				if (line.includes("ROUTES.")) continue;
				// Skip comments
				if (line.trim().startsWith("//") || line.trim().startsWith("<!--") || line.trim().startsWith("#")) continue;
				// Skip lines that are part of a href/goto but using template literals with ROUTES
				if (line.includes("ROUTES")) continue;

				// Check for goto("...") or href="..." patterns that contain internal paths
				// Match goto(`/path`) or href="/path" or goto("/path")
				for (const prefix of INTERNAL_PREFIXES) {
					// Match: goto(`/prefix...`) or goto("/prefix...")
					const gotoRegex = new RegExp(`goto\\([\`'"]${prefix}`);
					// Match: href="/prefix..." or href={`/prefix...`}
					const hrefRegex = new RegExp(`href=[\`'"]${prefix}`);
					// Match: window.location.href = "/prefix..."
					const locRegex = new RegExp(`location\\.href\\s*=\\s*[\`'"]${prefix}`);

					if (gotoRegex.test(line) || hrefRegex.test(line) || locRegex.test(line)) {
						const cleanLine = line.trim().substring(0, 120);
						violations.push({ file: path.relative(process.cwd(), file), line: i + 1, match: cleanLine });
					}
				}
			}
		}

		if (violations.length > 0) {
			const msg = violations
				.map((v) => `  ${v.file}:${v.line} — ${v.match}`)
				.join("\n");
			expect.fail(
				`Found ${violations.length} raw internal route string(s). Use ROUTES.*() instead:\n${msg}`,
			);
		}
	});
});
// #endregion RoutesLinkIntegrityTest
