import {
	existsSync,
	mkdirSync,
	readdirSync,
	readFileSync,
	renameSync,
	rmSync,
	statSync,
	writeFileSync,
} from "node:fs";
import { dirname, join } from "node:path";

import { requireProjectCwd } from "../../../../src/web/bridge-service.ts";
import { resolveSecurePath } from "../../../lib/secure-path.ts";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

const MAX_FILE_SIZE = 256 * 1024; // 256KB
const MAX_PROJECT_DEPTH = 6;

/** Directories to skip when listing the project root tree */
const PROJECT_SKIP_DIRS = new Set([
	"node_modules",
	".git",
	".next",
	".turbo",
	".vercel",
	".cache",
	".output",
	"dist",
	"build",
	"coverage",
	"__pycache__",
	".svelte-kit",
	".nuxt",
	".parcel-cache",
]);

type RootMode = "sf" | "project";

interface FileNode {
	name: string;
	type: "file" | "directory";
	children?: FileNode[];
}

function getSfRoot(projectCwd: string): string {
	return join(projectCwd, ".sf");
}

function getRootForMode(mode: RootMode, projectCwd: string): string {
	return mode === "project" ? projectCwd : getSfRoot(projectCwd);
}

function buildTree(
	dirPath: string,
	skipDirs?: Set<string>,
	depth = 0,
	maxDepth = Infinity,
): FileNode[] {
	if (!existsSync(dirPath)) return [];
	if (depth >= maxDepth) return [];

	const entries = readdirSync(dirPath, { withFileTypes: true });
	const nodes: FileNode[] = [];

	for (const entry of entries) {
		if (entry.name.startsWith(".")) continue;

		if (entry.isDirectory()) {
			if (skipDirs?.has(entry.name)) continue;
			const fullPath = join(dirPath, entry.name);
			nodes.push({
				name: entry.name,
				type: "directory",
				children: buildTree(fullPath, skipDirs, depth + 1, maxDepth),
			});
		} else if (entry.isFile()) {
			nodes.push({
				name: entry.name,
				type: "file",
			});
		}
	}

	nodes.sort((a, b) => {
		if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
		return a.name.localeCompare(b.name);
	});

	return nodes;
}

export async function GET(request: Request): Promise<Response> {
	const { searchParams } = new URL(request.url);
	const pathParam = searchParams.get("path");
	const rootParam = (searchParams.get("root") ?? "sf") as RootMode;

	if (rootParam !== "sf" && rootParam !== "project") {
		return Response.json(
			{ error: `Invalid root: must be "sf" or "project"` },
			{ status: 400 },
		);
	}

	const projectCwd = requireProjectCwd(request);
	const root = getRootForMode(rootParam, projectCwd);
	const headers = { "Cache-Control": "no-store" };

	// Mode A: return directory tree
	if (!pathParam) {
		if (!existsSync(root)) {
			return Response.json({ tree: [] }, { headers });
		}
		const skipDirs = rootParam === "project" ? PROJECT_SKIP_DIRS : undefined;
		const maxDepth = rootParam === "project" ? MAX_PROJECT_DEPTH : Infinity;
		return Response.json(
			{ tree: buildTree(root, skipDirs, 0, maxDepth) },
			{ headers },
		);
	}

	// Mode B: return file content
	const resolvedPath = resolveSecurePath(pathParam, root);
	if (!resolvedPath) {
		const label = rootParam === "project" ? "project root" : ".sf/";
		return Response.json(
			{
				error: `Invalid path: path must be relative within ${label} and cannot contain '..' or start with '/'`,
			},
			{ status: 400, headers },
		);
	}

	if (!existsSync(resolvedPath)) {
		return Response.json(
			{ error: `File not found: ${pathParam}` },
			{ status: 404, headers },
		);
	}

	const stat = statSync(resolvedPath);

	if (stat.isDirectory()) {
		return Response.json(
			{ error: `Path is a directory, not a file: ${pathParam}` },
			{ status: 400, headers },
		);
	}

	if (stat.size > MAX_FILE_SIZE) {
		return Response.json(
			{
				error: `File too large: ${pathParam} (${stat.size} bytes, max ${MAX_FILE_SIZE})`,
			},
			{ status: 413, headers },
		);
	}

	const content = readFileSync(resolvedPath, "utf-8");
	return Response.json({ content }, { headers });
}

export async function POST(request: Request): Promise<Response> {
	let body: Record<string, unknown>;
	try {
		body = await request.json();
	} catch {
		return Response.json({ error: "Invalid JSON body" }, { status: 400 });
	}

	const {
		path: pathParam,
		content,
		root: rootParam = "sf",
	} = body as {
		path?: string;
		content?: unknown;
		root?: string;
	};

	if (rootParam !== "sf" && rootParam !== "project") {
		return Response.json(
			{ error: `Invalid root: must be "sf" or "project"` },
			{ status: 400 },
		);
	}

	if (typeof content !== "string") {
		return Response.json(
			{ error: "Missing or invalid content: must be a string" },
			{ status: 400 },
		);
	}

	if (Buffer.byteLength(content, "utf-8") > MAX_FILE_SIZE) {
		return Response.json(
			{
				error: `Content too large: ${Buffer.byteLength(content, "utf-8")} bytes exceeds max ${MAX_FILE_SIZE}`,
			},
			{ status: 413 },
		);
	}

	const projectCwd = requireProjectCwd(request);
	const root = getRootForMode(rootParam as RootMode, projectCwd);

	if (typeof pathParam !== "string" || pathParam.length === 0) {
		return Response.json(
			{ error: "Missing or invalid path: must be a non-empty string" },
			{ status: 400 },
		);
	}

	const resolvedPath = resolveSecurePath(pathParam, root);
	if (!resolvedPath) {
		const label = rootParam === "project" ? "project root" : ".sf/";
		return Response.json(
			{
				error: `Invalid path: path must be relative within ${label} and cannot contain '..' or start with '/'`,
			},
			{ status: 400 },
		);
	}

	if (!existsSync(dirname(resolvedPath))) {
		return Response.json(
			{ error: "Parent directory does not exist" },
			{ status: 404 },
		);
	}

	writeFileSync(resolvedPath, content, "utf-8");
	return Response.json({ success: true });
}

/** PATCH — move/rename a file or directory */
export async function PATCH(request: Request): Promise<Response> {
	let body: Record<string, unknown>;
	try {
		body = await request.json();
	} catch {
		return Response.json({ error: "Invalid JSON body" }, { status: 400 });
	}

	const {
		from,
		to,
		root: rootParam = "sf",
	} = body as {
		from?: string;
		to?: string;
		root?: string;
	};

	if (rootParam !== "sf" && rootParam !== "project") {
		return Response.json(
			{ error: `Invalid root: must be "sf" or "project"` },
			{ status: 400 },
		);
	}

	if (typeof from !== "string" || from.length === 0) {
		return Response.json(
			{ error: "Missing or invalid 'from': must be a non-empty string" },
			{ status: 400 },
		);
	}

	if (typeof to !== "string" || to.length === 0) {
		return Response.json(
			{ error: "Missing or invalid 'to': must be a non-empty string" },
			{ status: 400 },
		);
	}

	const projectCwd = requireProjectCwd(request);
	const root = getRootForMode(rootParam as RootMode, projectCwd);
	const label = rootParam === "project" ? "project root" : ".sf/";

	const resolvedFrom = resolveSecurePath(from, root);
	if (!resolvedFrom) {
		return Response.json(
			{ error: `Invalid 'from' path: must be relative within ${label}` },
			{ status: 400 },
		);
	}

	const resolvedTo = resolveSecurePath(to, root);
	if (!resolvedTo) {
		return Response.json(
			{ error: `Invalid 'to' path: must be relative within ${label}` },
			{ status: 400 },
		);
	}

	if (!existsSync(resolvedFrom)) {
		return Response.json(
			{ error: `Source not found: ${from}` },
			{ status: 404 },
		);
	}

	if (existsSync(resolvedTo)) {
		return Response.json(
			{ error: `Destination already exists: ${to}` },
			{ status: 409 },
		);
	}

	if (!existsSync(dirname(resolvedTo))) {
		return Response.json(
			{ error: `Destination directory does not exist: ${dirname(to)}` },
			{ status: 404 },
		);
	}

	try {
		renameSync(resolvedFrom, resolvedTo);
	} catch (err) {
		return Response.json(
			{
				error: `Move failed: ${err instanceof Error ? err.message : String(err)}`,
			},
			{ status: 500 },
		);
	}

	return Response.json({ success: true, from, to });
}

/** DELETE — delete a file or directory */
export async function DELETE(request: Request): Promise<Response> {
	const { searchParams } = new URL(request.url);
	const pathParam = searchParams.get("path");
	const rootParam = (searchParams.get("root") ?? "sf") as RootMode;

	if (rootParam !== "sf" && rootParam !== "project") {
		return Response.json(
			{ error: `Invalid root: must be "sf" or "project"` },
			{ status: 400 },
		);
	}

	if (!pathParam || pathParam.length === 0) {
		return Response.json(
			{ error: "Missing 'path' query parameter" },
			{ status: 400 },
		);
	}

	const projectCwd = requireProjectCwd(request);
	const root = getRootForMode(rootParam, projectCwd);
	const label = rootParam === "project" ? "project root" : ".sf/";

	const resolvedPath = resolveSecurePath(pathParam, root);
	if (!resolvedPath) {
		return Response.json(
			{ error: `Invalid path: must be relative within ${label}` },
			{ status: 400 },
		);
	}

	if (!existsSync(resolvedPath)) {
		return Response.json({ error: `Not found: ${pathParam}` }, { status: 404 });
	}

	try {
		rmSync(resolvedPath, { recursive: true });
	} catch (err) {
		return Response.json(
			{
				error: `Delete failed: ${err instanceof Error ? err.message : String(err)}`,
			},
			{ status: 500 },
		);
	}

	return Response.json({ success: true });
}

/** PUT — create a new file or directory */
export async function PUT(request: Request): Promise<Response> {
	let body: Record<string, unknown>;
	try {
		body = await request.json();
	} catch {
		return Response.json({ error: "Invalid JSON body" }, { status: 400 });
	}

	const {
		path: pathParam,
		type = "file",
		root: rootParam = "sf",
	} = body as {
		path?: string;
		type?: "file" | "directory";
		root?: string;
	};

	if (rootParam !== "sf" && rootParam !== "project") {
		return Response.json(
			{ error: `Invalid root: must be "sf" or "project"` },
			{ status: 400 },
		);
	}

	if (typeof pathParam !== "string" || pathParam.length === 0) {
		return Response.json(
			{ error: "Missing or invalid 'path'" },
			{ status: 400 },
		);
	}

	if (type !== "file" && type !== "directory") {
		return Response.json(
			{ error: `Invalid type: must be "file" or "directory"` },
			{ status: 400 },
		);
	}

	const projectCwd = requireProjectCwd(request);
	const root = getRootForMode(rootParam as RootMode, projectCwd);
	const label = rootParam === "project" ? "project root" : ".sf/";

	const resolvedPath = resolveSecurePath(pathParam, root);
	if (!resolvedPath) {
		return Response.json(
			{ error: `Invalid path: must be relative within ${label}` },
			{ status: 400 },
		);
	}

	if (existsSync(resolvedPath)) {
		return Response.json(
			{ error: `Already exists: ${pathParam}` },
			{ status: 409 },
		);
	}

	if (!existsSync(dirname(resolvedPath))) {
		return Response.json(
			{ error: `Parent directory does not exist: ${dirname(pathParam)}` },
			{ status: 404 },
		);
	}

	try {
		if (type === "directory") {
			mkdirSync(resolvedPath);
		} else {
			writeFileSync(resolvedPath, "", "utf-8");
		}
	} catch (err) {
		return Response.json(
			{
				error: `Create failed: ${err instanceof Error ? err.message : String(err)}`,
			},
			{ status: 500 },
		);
	}

	return Response.json({ success: true });
}
