import { spawnSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";

export interface PythonRuntimeRequirement {
	version: string;
	source: "pyproject.toml" | "mise.toml";
}

export interface PythonRuntimeInfo {
	executable: string;
	version: string;
}

export interface ProjectRuntimeGuardResult {
	blocked: boolean;
	message: string;
}

function parseVersion(value: string): [number, number, number] | null {
	const match = value.match(/(\d+)\.(\d+)(?:\.(\d+))?/);
	if (!match) return null;
	return [
		Number.parseInt(match[1]!, 10),
		Number.parseInt(match[2]!, 10),
		Number.parseInt(match[3] ?? "0", 10),
	];
}

function compareVersions(a: string, b: string): number {
	const left = parseVersion(a);
	const right = parseVersion(b);
	if (!left || !right) return 0;
	for (let i = 0; i < 3; i++) {
		const diff = left[i]! - right[i]!;
		if (diff !== 0) return diff;
	}
	return 0;
}

/**
 * Detect whether a shell command invokes the ambient Python interpreter.
 *
 * Purpose: identify commands where SF must validate the project-declared
 * Python runtime before execution, preventing agents from treating syntax
 * failures from the wrong host Python as source-code defects.
 *
 * Consumer: bash tool project-runtime guard.
 */
export function invokesBarePython(command: string): boolean {
	const stripped = command
		.replace(/^\s*(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S+)\s+)*/, "")
		.trim();
	return /^(python|python3)(?:\s|$)/.test(stripped);
}

function readPyprojectRequirement(
	cwd: string,
): PythonRuntimeRequirement | null {
	const path = join(cwd, "pyproject.toml");
	if (!existsSync(path)) return null;
	const raw = readFileSync(path, "utf-8");
	const match = raw.match(/^\s*requires-python\s*=\s*["']([^"']+)["']/m);
	if (!match) return null;
	const min = match[1]!.match(/>=\s*([0-9]+(?:\.[0-9]+){1,2})/);
	if (!min) return null;
	return { version: min[1]!, source: "pyproject.toml" };
}

function readMisePythonRequirement(
	cwd: string,
): PythonRuntimeRequirement | null {
	const path = join(cwd, "mise.toml");
	if (!existsSync(path)) return null;
	const raw = readFileSync(path, "utf-8");
	const match = raw.match(/^\s*python\s*=\s*["']([^"']+)["']/m);
	if (!match) return null;
	const version = match[1]!.match(/([0-9]+(?:\.[0-9]+){1,2})/);
	if (!version) return null;
	return { version: version[1]!, source: "mise.toml" };
}

/**
 * Resolve the strongest project-declared Python minimum from local config.
 *
 * Purpose: make the project, not the host PATH, authoritative for Python test
 * and tooling commands.
 *
 * Consumer: bash tool project-runtime guard.
 */
export function readPythonRuntimeRequirement(
	cwd: string,
): PythonRuntimeRequirement | null {
	const requirements = [
		readPyprojectRequirement(cwd),
		readMisePythonRequirement(cwd),
	].filter((item): item is PythonRuntimeRequirement => item !== null);
	if (requirements.length === 0) return null;
	return requirements.sort((a, b) => compareVersions(b.version, a.version))[0]!;
}

function resolveActivePython(
	cwd: string,
	env: NodeJS.ProcessEnv,
): PythonRuntimeInfo | null {
	const script =
		"import sys; print(sys.executable); print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')";
	const result = spawnSync("python", ["-c", script], {
		cwd,
		env,
		encoding: "utf-8",
		timeout: 3000,
	});
	if (result.status !== 0 || !result.stdout) return null;
	const [executable, version] = result.stdout.trim().split(/\r?\n/);
	if (!executable || !version) return null;
	return { executable, version };
}

export function evaluatePythonRuntimeGuard(
	command: string,
	cwd: string,
	activePython: PythonRuntimeInfo | null,
): ProjectRuntimeGuardResult | null {
	if (!invokesBarePython(command)) return null;
	const requirement = readPythonRuntimeRequirement(cwd);
	if (!requirement) return null;
	if (!activePython) {
		return {
			blocked: true,
			message:
				`BLOCKED: project Python runtime could not be resolved before running \`${command}\`.\n\n` +
				`Project declares Python >=${requirement.version} in ${requirement.source}. ` +
				"Do not edit source syntax to satisfy an unknown host interpreter; activate the project runtime and rerun the check.",
		};
	}
	if (compareVersions(activePython.version, requirement.version) >= 0)
		return null;
	return {
		blocked: true,
		message:
			`BLOCKED: project Python runtime mismatch before running \`${command}\`.\n\n` +
			`Project declares Python >=${requirement.version} in ${requirement.source}, ` +
			`but bare \`python\` resolves to ${activePython.version} at ${activePython.executable}.\n\n` +
			"Do not modify project code to satisfy the wrong interpreter. Activate the project runtime or run the repo-declared Python command, then retry verification.",
	};
}

export function checkProjectRuntimeForCommand(
	command: string,
	cwd: string,
	env: NodeJS.ProcessEnv,
): ProjectRuntimeGuardResult | null {
	if (!invokesBarePython(command)) return null;
	return evaluatePythonRuntimeGuard(
		command,
		cwd,
		resolveActivePython(cwd, env),
	);
}
