import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";

import { Type } from "@mariozechner/pi-ai";
import { defineTool, type ExtensionAPI } from "@mariozechner/pi-coding-agent";

const REQUEST_ID_RE = /^req-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
const ACTION_ID_RE = /^act-[a-z0-9_-]{1,80}$/;
const AGENT_RE = /^[a-z][a-z0-9_-]{0,31}$/;
const ABS_PATH_RE = /^\/[A-Za-z0-9._/-]{1,240}$/;
const SAFE_SEGMENT_RE = /^[A-Za-z0-9._-]+$/;
const ALLOWED_PATH_PREFIXES = [
	"/input/",
	"/output/",
	"/messages/",
	"/tool-templates/",
	"/results/",
	"/release/",
];

const ActionTypeSchema = Type.Union([Type.Literal("run_real_data"), Type.Literal("release_results")]);

const PathSchema = Type.String({
	description: "Absolute normalized guest path under an approved mount prefix",
	pattern: "^/[A-Za-z0-9._/-]{1,240}$",
});

const TemplateVarSchema = Type.Object({
	name: Type.String({
		description: "Template variable name",
		pattern: "^[a-z][a-z0-9_]{0,31}$",
	}),
	value: Type.String({
		description: "Short template variable value",
		minLength: 1,
		maxLength: 240,
	}),
});

const RequestControlledActionParams = Type.Object({
	action_id: Type.String({
		description: "Stable controlled action id, for example act-run-real-req-...",
		pattern: "^act-[a-z0-9_-]{1,80}$",
	}),
	request_id: Type.String({
		description: "Canonical rho request id in req-<uuid> form",
		pattern: "^req-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
	}),
	requested_by: Type.String({
		description: "Agent asking for the controlled action",
		pattern: "^[a-z][a-z0-9_-]{0,31}$",
	}),
	requested_for: Type.String({
		description: "Agent on whose behalf the action will be performed",
		pattern: "^[a-z][a-z0-9_-]{0,31}$",
	}),
	action_type: ActionTypeSchema,
	summary: Type.String({
		description: "One line summary shown to the human operator",
		minLength: 1,
		maxLength: 240,
	}),
	reason: Type.String({
		description: "Short explanation for why the action is needed",
		minLength: 1,
		maxLength: 1200,
	}),
	input_path: PathSchema,
	output_path: PathSchema,
	script_path: Type.Optional(PathSchema),
	message_path: Type.Optional(PathSchema),
	manifest_path: Type.Optional(PathSchema),
	notification_path: Type.Optional(PathSchema),
	supporting_paths: Type.Optional(
		Type.Array(PathSchema, {
			description: "Additional guest paths the operator may want to inspect",
			maxItems: 8,
		}),
	),
	template_vars: Type.Optional(
		Type.Array(TemplateVarSchema, {
			description: "Short validated name/value pairs reserved for future template rendering",
			maxItems: 8,
		}),
	),
});

const GetControlledActionStatusParams = Type.Object({
	action_id: Type.String({
		description: "Stable controlled action id",
		pattern: "^act-[a-z0-9_-]{1,80}$",
	}),
});

function validateRegex(value: string, label: string, pattern: RegExp): void {
	if (!pattern.test(value)) {
		throw new Error(`${label} failed validation`);
	}
}

function validateText(value: string, label: string, maxLength: number): void {
	if (value.length === 0 || value.length > maxLength) {
		throw new Error(`${label} must be between 1 and ${maxLength} characters`);
	}
	if (value.includes("\u0000")) {
		throw new Error(`${label} must not contain NUL bytes`);
	}
}

function validateGuestPath(value: string, label: string): void {
	validateRegex(value, label, ABS_PATH_RE);
	if (!ALLOWED_PATH_PREFIXES.some((prefix) => value.startsWith(prefix))) {
		throw new Error(`${label} must live under ${ALLOWED_PATH_PREFIXES.join(", ")}`);
	}

	const normalized = path.posix.normalize(value);
	if (normalized !== value) {
		throw new Error(`${label} must already be normalized`);
	}

	for (const segment of value.split("/")) {
		if (!segment || segment === ".") continue;
		if (segment === "..") {
			throw new Error(`${label} must not contain parent directory traversal`);
		}
		if (!SAFE_SEGMENT_RE.test(segment)) {
			throw new Error(`${label} contains an unsafe path segment: ${segment}`);
		}
	}
}

function outboxPath(actionId: string): string {
	return `/control/outbox/${actionId}.json`;
}

function inboxPath(actionId: string): string {
	return `/control/inbox/${actionId}.json`;
}

const requestControlledActionTool = defineTool({
	name: "request_controlled_action",
	label: "Request Controlled Action",
	description:
		"Queue a strict host-executed action that requires human approval and deterministic execution outside the sandbox.",
	promptSnippet:
		"request_controlled_action queues a validated host-controlled action in /control/outbox. Use it for protected actions such as run_real_data or release_results.",
	promptGuidelines: [
		"Read the templates under /tool-templates before calling request_controlled_action.",
		"Only pass normalized guest paths under approved prefixes.",
		"Do not invent host paths, shell commands, raw URLs, or extra fields.",
		"Use run_real_data for protected execution and release_results for protected publication.",
	],
	parameters: RequestControlledActionParams,

	async execute(_toolCallId, params) {
		validateRegex(params.action_id, "action_id", ACTION_ID_RE);
		validateRegex(params.request_id, "request_id", REQUEST_ID_RE);
		validateRegex(params.requested_by, "requested_by", AGENT_RE);
		validateRegex(params.requested_for, "requested_for", AGENT_RE);
		validateText(params.summary.trim(), "summary", 240);
		validateText(params.reason.trim(), "reason", 1200);
		validateGuestPath(params.input_path, "input_path");
		validateGuestPath(params.output_path, "output_path");

		if (params.script_path) validateGuestPath(params.script_path, "script_path");
		if (params.message_path) validateGuestPath(params.message_path, "message_path");
		if (params.manifest_path) validateGuestPath(params.manifest_path, "manifest_path");
		if (params.notification_path) validateGuestPath(params.notification_path, "notification_path");

		const supportingPaths = params.supporting_paths ?? [];
		for (const value of supportingPaths) {
			validateGuestPath(value, "supporting_paths");
		}

		const templateVars = params.template_vars ?? [];
		for (const item of templateVars) {
			validateRegex(item.name, "template_vars.name", /^[a-z][a-z0-9_]{0,31}$/);
			validateText(item.value, "template_vars.value", 240);
		}

		if (params.action_type === "run_real_data" && !params.script_path) {
			throw new Error("script_path is required for run_real_data");
		}

		if (params.action_type === "release_results") {
			if (!params.manifest_path) {
				throw new Error("manifest_path is required for release_results");
			}
			if (!params.notification_path) {
				throw new Error("notification_path is required for release_results");
			}
		}

		const payload = {
			version: 1,
			kind: "controlled_action",
			action: {
				action_id: params.action_id,
				request_id: params.request_id,
				requested_by: params.requested_by,
				requested_for: params.requested_for,
				action_type: params.action_type,
				summary: params.summary.trim(),
				reason: params.reason.trim(),
				input_path: params.input_path,
				output_path: params.output_path,
				script_path: params.script_path,
				message_path: params.message_path,
				manifest_path: params.manifest_path,
				notification_path: params.notification_path,
				supporting_paths: supportingPaths,
				template_vars: templateVars,
			},
		};

		await mkdir("/control/outbox", { recursive: true });
		const target = outboxPath(params.action_id);
		await writeFile(target, JSON.stringify(payload, null, 2) + "\n", "utf8");

		return {
			content: [{ type: "text", text: `Queued controlled action at ${target}` }],
			details: {
				status: "queued",
				action_id: params.action_id,
				outbox_path: target,
			},
		};
	},
});

const getControlledActionStatusTool = defineTool({
	name: "get_controlled_action_status",
	label: "Get Controlled Action Status",
	description: "Read the current host-side status for a previously queued controlled action.",
	promptSnippet:
		"get_controlled_action_status reads /control/inbox for an action and returns pending, approved, denied, completed, or failed.",
	parameters: GetControlledActionStatusParams,

	async execute(_toolCallId, params) {
		validateRegex(params.action_id, "action_id", ACTION_ID_RE);
		const target = inboxPath(params.action_id);

		try {
			const raw = await readFile(target, "utf8");
			const parsed = JSON.parse(raw) as {
				status?: { state?: string; note?: string; actor?: string; output_path?: string };
			};
			const state = typeof parsed.status?.state === "string" ? parsed.status.state : "pending";
			const note = typeof parsed.status?.note === "string" ? parsed.status.note : "";
			const actor = typeof parsed.status?.actor === "string" ? parsed.status.actor : "";
			const outputPath = typeof parsed.status?.output_path === "string" ? parsed.status.output_path : "";
			return {
				content: [
					{
						type: "text",
						text: note
							? `Controlled action ${params.action_id}: ${state} (${note})`
							: `Controlled action ${params.action_id}: ${state}`,
					},
				],
				details: {
					action_id: params.action_id,
					status: state,
					actor,
					note,
					output_path: outputPath,
					inbox_path: target,
				},
			};
		} catch {
			return {
				content: [{ type: "text", text: `Controlled action ${params.action_id}: pending` }],
				details: {
					action_id: params.action_id,
					status: "pending",
					inbox_path: target,
				},
			};
		}
	},
});

export default function controlledActionBridge(pi: ExtensionAPI) {
	pi.registerTool(requestControlledActionTool);
	pi.registerTool(getControlledActionStatusTool);
}
