import { execFile } from "node:child_process";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import type {
	CleanupData,
	CleanupResult,
} from "../../web/lib/remaining-command-types.ts";
import { getLogger } from "../logger.js";
import { resolveBridgeRuntimeConfig } from "./bridge-service.ts";
import {
	buildSubprocessPrefixArgs,
	resolveSubprocessModule,
} from "./ts-subprocess-flags.ts";

const _log = getLogger("sf.web.cleanup-service");

const CLEANUP_MAX_BUFFER = 2 * 1024 * 1024;
const CLEANUP_MODULE_ENV = "SF_CLEANUP_MODULE";

function resolveTsLoaderPath(packageRoot: string): string {
	return join(
		packageRoot,
		"src",
		"resources",
		"extensions",
		"sf",
		"tests",
		"resolve-ts.mjs",
	);
}

/**
 * Collects cleanup data (SF branches and snapshot refs) via a child process.
 * Child-process pattern required because native-git-bridge.ts uses .ts imports
 * that need the resolve-ts.mjs loader.
 */
export async function collectCleanupData(
	projectCwdOverride?: string,
): Promise<CleanupData> {
	const config = resolveBridgeRuntimeConfig(undefined, projectCwdOverride);
	const { packageRoot, projectCwd } = config;

	const resolveTsLoader = resolveTsLoaderPath(packageRoot);
	const moduleResolution = resolveSubprocessModule(
		packageRoot,
		"resources/extensions/sf/native-git-bridge.ts",
	);
	const cleanupModulePath = moduleResolution.modulePath;

	if (
		!moduleResolution.useCompiledJs &&
		(!existsSync(resolveTsLoader) || !existsSync(cleanupModulePath))
	) {
		throw new Error(
			`cleanup data provider not found; checked=${resolveTsLoader},${cleanupModulePath}`,
		);
	}
	if (moduleResolution.useCompiledJs && !existsSync(cleanupModulePath)) {
		throw new Error(
			`cleanup data provider not found; checked=${cleanupModulePath}`,
		);
	}

	const script = [
		'const { pathToFileURL } = await import("node:url");',
		`const mod = await import(pathToFileURL(process.env.${CLEANUP_MODULE_ENV}).href);`,
		"const basePath = process.env.SF_CLEANUP_BASE;",
		// Get all SF branches
		"let branches = [];",
		'try { branches = mod.nativeBranchList(basePath, "sf/*"); } catch (e) { _log.warn("[cleanup-service] nativeBranchList failed:", { error: e }); }',
		// Detect main branch and find which SF branches are merged
		'let mainBranch = "main";',
		'try { mainBranch = mod.nativeDetectMainBranch(basePath); } catch (e) { _log.warn("[cleanup-service] nativeDetectMainBranch failed — falling back to \\"main\\":", { error: e }); }',
		"let merged = [];",
		'try { merged = mod.nativeBranchListMerged(basePath, mainBranch, "sf/*"); } catch (e) { _log.warn("[cleanup-service] nativeBranchListMerged failed:", { error: e }); }',
		"const mergedSet = new Set(merged);",
		"const branchList = branches.map(b => ({ name: b, merged: mergedSet.has(b) }));",
		// Get snapshot refs
		"let refs = [];",
		'try { refs = mod.nativeForEachRef(basePath, "refs/sf/snapshots/"); } catch (e) { _log.warn("[cleanup-service] nativeForEachRef failed:", { error: e }); }',
		"const snapshotList = refs.map(r => {",
		'  const parts = r.split(" ");',
		'  return { ref: parts[0] || r, date: parts.length > 1 ? parts.slice(1).join(" ") : "" };',
		"});",
		"process.stdout.write(JSON.stringify({ branches: branchList, snapshots: snapshotList }));",
	].join(" ");

	const prefixArgs = buildSubprocessPrefixArgs(
		packageRoot,
		moduleResolution,
		pathToFileURL(resolveTsLoader).href,
	);

	return await new Promise<CleanupData>((resolveResult, reject) => {
		execFile(
			process.execPath,
			[...prefixArgs, "--eval", script],
			{
				cwd: packageRoot,
				env: {
					...process.env,
					[CLEANUP_MODULE_ENV]: cleanupModulePath,
					SF_CLEANUP_BASE: projectCwd,
				},
				maxBuffer: CLEANUP_MAX_BUFFER,
				windowsHide: true,
			},
			(error, stdout, stderr) => {
				if (error) {
					reject(
						new Error(
							`cleanup data subprocess failed: ${stderr || error.message}`,
						),
					);
					return;
				}

				try {
					resolveResult(JSON.parse(stdout) as CleanupData);
				} catch (parseError) {
					reject(
						new Error(
							`cleanup data subprocess returned invalid JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
						),
					);
				}
			},
		);
	});
}

/**
 * Executes cleanup operations (branch deletion and snapshot pruning) via a child process.
 * Child-process pattern required because nativeBranchDelete and nativeUpdateRef
 * modify git state using .ts imports.
 */
export async function executeCleanup(
	deleteBranches: string[],
	pruneSnapshots: string[],
	projectCwdOverride?: string,
): Promise<CleanupResult> {
	const config = resolveBridgeRuntimeConfig(undefined, projectCwdOverride);
	const { packageRoot, projectCwd } = config;

	const resolveTsLoader = resolveTsLoaderPath(packageRoot);
	const moduleResolution = resolveSubprocessModule(
		packageRoot,
		"resources/extensions/sf/native-git-bridge.ts",
	);
	const cleanupModulePath = moduleResolution.modulePath;

	if (
		!moduleResolution.useCompiledJs &&
		(!existsSync(resolveTsLoader) || !existsSync(cleanupModulePath))
	) {
		throw new Error(
			`cleanup service modules not found; checked=${resolveTsLoader},${cleanupModulePath}`,
		);
	}
	if (moduleResolution.useCompiledJs && !existsSync(cleanupModulePath)) {
		throw new Error(
			`cleanup service modules not found; checked=${cleanupModulePath}`,
		);
	}

	const script = [
		'const { pathToFileURL } = await import("node:url");',
		`const mod = await import(pathToFileURL(process.env.${CLEANUP_MODULE_ENV}).href);`,
		"const basePath = process.env.SF_CLEANUP_BASE;",
		'const branches = JSON.parse(process.env.SF_CLEANUP_BRANCHES || "[]");',
		'const snapshots = JSON.parse(process.env.SF_CLEANUP_SNAPSHOTS || "[]");',
		"let deletedBranches = 0;",
		"let prunedSnapshots = 0;",
		"const errors = [];",
		"for (const branch of branches) {",
		"  try { mod.nativeBranchDelete(basePath, branch, true); deletedBranches++; }",
		// biome-ignore lint/suspicious/noTemplateCurlyInString: intentional literal — JS code being constructed for subprocess
		"  catch (e) { errors.push(`Branch ${branch}: ${e.message}`); }",
		"}",
		"for (const ref of snapshots) {",
		"  try { mod.nativeUpdateRef(basePath, ref); prunedSnapshots++; }",
		// biome-ignore lint/suspicious/noTemplateCurlyInString: intentional literal — JS code being constructed for subprocess
		"  catch (e) { errors.push(`Ref ${ref}: ${e.message}`); }",
		"}",
		"const parts = [];",
		// biome-ignore lint/suspicious/noTemplateCurlyInString: intentional literal — JS code being constructed for subprocess
		"if (deletedBranches > 0) parts.push(`Deleted ${deletedBranches} branch(es)`);",
		// biome-ignore lint/suspicious/noTemplateCurlyInString: intentional literal — JS code being constructed for subprocess
		"if (prunedSnapshots > 0) parts.push(`Pruned ${prunedSnapshots} snapshot(s)`);",
		// biome-ignore lint/suspicious/noTemplateCurlyInString: intentional literal — JS code being constructed for subprocess
		'if (errors.length > 0) parts.push(`Errors: ${errors.join("; ")}`);',
		'const message = parts.length > 0 ? parts.join(". ") : "No items to clean up";',
		"process.stdout.write(JSON.stringify({ deletedBranches, prunedSnapshots, message }));",
	].join(" ");

	const prefixArgs = buildSubprocessPrefixArgs(
		packageRoot,
		moduleResolution,
		pathToFileURL(resolveTsLoader).href,
	);

	return await new Promise<CleanupResult>((resolveResult, reject) => {
		execFile(
			process.execPath,
			[...prefixArgs, "--eval", script],
			{
				cwd: packageRoot,
				env: {
					...process.env,
					[CLEANUP_MODULE_ENV]: cleanupModulePath,
					SF_CLEANUP_BASE: projectCwd,
					SF_CLEANUP_BRANCHES: JSON.stringify(deleteBranches),
					SF_CLEANUP_SNAPSHOTS: JSON.stringify(pruneSnapshots),
				},
				maxBuffer: CLEANUP_MAX_BUFFER,
				windowsHide: true,
			},
			(error, stdout, stderr) => {
				if (error) {
					reject(
						new Error(`cleanup subprocess failed: ${stderr || error.message}`),
					);
					return;
				}

				try {
					resolveResult(JSON.parse(stdout) as CleanupResult);
				} catch (parseError) {
					reject(
						new Error(
							`cleanup subprocess returned invalid JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
						),
					);
				}
			},
		);
	});
}
