import { execFile } from "node:child_process";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import type {
	DoctorFixResult,
	DoctorReport,
} from "../../web/lib/diagnostics-types.ts";
import { resolveBridgeRuntimeConfig } from "./bridge-service.ts";
import {
	buildSubprocessPrefixArgs,
	resolveSubprocessModule,
} from "./ts-subprocess-flags.ts";

const DOCTOR_MAX_BUFFER = 2 * 1024 * 1024;
const DOCTOR_MODULE_ENV = "SF_DOCTOR_MODULE";

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

function runDoctorChild(
	packageRoot: string,
	projectCwd: string,
	script: string,
	resolveTsLoader: string,
	doctorModulePath: string,
	moduleResolution: { modulePath: string; useCompiledJs: boolean },
	scope?: string,
): Promise<string> {
	const prefixArgs = buildSubprocessPrefixArgs(
		packageRoot,
		moduleResolution,
		pathToFileURL(resolveTsLoader).href,
	);
	return new Promise<string>((resolveResult, reject) => {
		execFile(
			process.execPath,
			[...prefixArgs, "--eval", script],
			{
				cwd: packageRoot,
				env: {
					...process.env,
					[DOCTOR_MODULE_ENV]: doctorModulePath,
					SF_DOCTOR_BASE: projectCwd,
					SF_DOCTOR_SCOPE: scope ?? "",
				},
				maxBuffer: DOCTOR_MAX_BUFFER,
				windowsHide: true,
			},
			(error, stdout, stderr) => {
				if (error) {
					reject(
						new Error(`doctor subprocess failed: ${stderr || error.message}`),
					);
					return;
				}
				resolveResult(stdout);
			},
		);
	});
}

/**
 * Loads doctor diagnostic data (GET — read-only, no fixes applied).
 * Returns full issues array + summary for the doctor panel.
 */
export async function collectDoctorData(
	scope?: string,
	projectCwdOverride?: string,
): Promise<DoctorReport> {
	const config = resolveBridgeRuntimeConfig(undefined, projectCwdOverride);
	const { packageRoot, projectCwd } = config;

	const resolveTsLoader = resolveTsLoaderPath(packageRoot);
	const moduleResolution = resolveSubprocessModule(
		packageRoot,
		"resources/extensions/sf/doctor.ts",
	);
	const doctorModulePath = moduleResolution.modulePath;

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

	const script = [
		'const { pathToFileURL } = await import("node:url");',
		`const mod = await import(pathToFileURL(process.env.${DOCTOR_MODULE_ENV}).href);`,
		"const basePath = process.env.SF_DOCTOR_BASE;",
		"const scope = process.env.SF_DOCTOR_SCOPE || undefined;",
		"const report = await mod.runSFDoctor(basePath, { fix: false, scope });",
		"const summary = mod.summarizeDoctorIssues(report.issues);",
		"const result = {",
		"  ok: report.ok,",
		"  issues: report.issues,",
		"  fixesApplied: report.fixesApplied,",
		"  summary,",
		"};",
		"process.stdout.write(JSON.stringify(result));",
	].join(" ");

	const stdout = await runDoctorChild(
		packageRoot,
		projectCwd,
		script,
		resolveTsLoader,
		doctorModulePath,
		moduleResolution,
		scope,
	);

	try {
		return JSON.parse(stdout) as DoctorReport;
	} catch (parseError) {
		throw new Error(
			`doctor subprocess returned invalid JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
		);
	}
}

/**
 * Applies doctor fixes (POST — mutating action).
 * Returns fix result with list of applied fixes.
 */
export async function applyDoctorFixes(
	scope?: string,
	projectCwdOverride?: string,
): Promise<DoctorFixResult> {
	const config = resolveBridgeRuntimeConfig(undefined, projectCwdOverride);
	const { packageRoot, projectCwd } = config;

	const resolveTsLoader = resolveTsLoaderPath(packageRoot);
	const moduleResolution = resolveSubprocessModule(
		packageRoot,
		"resources/extensions/sf/doctor.ts",
	);
	const doctorModulePath = moduleResolution.modulePath;

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

	const script = [
		'const { pathToFileURL } = await import("node:url");',
		`const mod = await import(pathToFileURL(process.env.${DOCTOR_MODULE_ENV}).href);`,
		"const basePath = process.env.SF_DOCTOR_BASE;",
		"const scope = process.env.SF_DOCTOR_SCOPE || undefined;",
		"const report = await mod.runSFDoctor(basePath, { fix: true, scope });",
		"const result = {",
		"  ok: report.ok,",
		"  fixesApplied: report.fixesApplied,",
		"};",
		"process.stdout.write(JSON.stringify(result));",
	].join(" ");

	const stdout = await runDoctorChild(
		packageRoot,
		projectCwd,
		script,
		resolveTsLoader,
		doctorModulePath,
		moduleResolution,
		scope,
	);

	try {
		return JSON.parse(stdout) as DoctorFixResult;
	} catch (parseError) {
		throw new Error(
			`doctor fix subprocess returned invalid JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
		);
	}
}
