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 RHO_GITHUB_ID_RE = /^rho:\/\/id\/github\/[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/", "/results/", "/release/"];

const ToolIdSchema = Type.Union([
	Type.Literal("run_mock"),
	Type.Literal("run_real"),
	Type.Literal("publish_results"),
]);

const ActionTypeSchema = Type.Union([
	Type.Literal("run_mock_data"),
	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 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}$",
	}),
	tool_id: ToolIdSchema,
	requested_by: Type.String({
		description: "Agent or Rho identity asking for the controlled action",
		pattern: "^(?:[a-z][a-z0-9_-]{0,31}|rho://id/github/[a-z][a-z0-9_-]{0,31})$",
	}),
	requested_for: Type.String({
		description: "Agent or Rho identity on whose behalf the action will be performed",
		pattern: "^(?:[a-z][a-z0-9_-]{0,31}|rho://id/github/[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,
	}),
	output_path: PathSchema,
	input_path: Type.Optional(PathSchema),
	script_path: Type.Optional(PathSchema),
});

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

type RequestControlledActionParamsValue = {
	action_id: string;
	request_id: string;
	tool_id: "run_mock" | "run_real" | "publish_results";
	requested_by: string;
	requested_for: string;
	action_type: "run_mock_data" | "run_real_data" | "release_results";
	summary: string;
	reason: string;
	output_path: string;
	input_path?: string;
	script_path?: string;
};

function controlRoot(): string {
	return process.env.RHO_CONTROL_ROOT || "/control";
}

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

function validateActor(value: string, label: string): void {
	if (!AGENT_RE.test(value) && !RHO_GITHUB_ID_RE.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 `${controlRoot()}/outbox/${actionId}.json`;
}

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

function validateToolPair(toolId: string, actionType: string): void {
	const expected = {
		run_mock: "run_mock_data",
		run_real: "run_real_data",
		publish_results: "release_results",
	}[toolId];
	if (expected !== actionType) {
		throw new Error(`tool_id ${toolId} cannot request action_type ${actionType}`);
	}
}

function controlledActionPayload(params: RequestControlledActionParamsValue) {
	validateRegex(params.action_id, "action_id", ACTION_ID_RE);
	validateRegex(params.request_id, "request_id", REQUEST_ID_RE);
	validateActor(params.requested_by, "requested_by");
	validateActor(params.requested_for, "requested_for");
	validateToolPair(params.tool_id, params.action_type);
	validateText(params.summary.trim(), "summary", 240);
	validateText(params.reason.trim(), "reason", 1200);
	validateGuestPath(params.output_path, "output_path");
	if (params.input_path) validateGuestPath(params.input_path, "input_path");
	if (params.script_path) validateGuestPath(params.script_path, "script_path");

	if ((params.action_type === "run_mock_data" || params.action_type === "run_real_data") && !params.script_path) {
		throw new Error("script_path is required for run actions");
	}
	if (params.action_type === "release_results" && !params.input_path) {
		throw new Error("input_path is required for release_results");
	}

	return {
		version: 1,
		kind: "controlled_action",
		action: {
			action_id: params.action_id,
			request_id: params.request_id,
			tool_id: params.tool_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,
			script_path: params.script_path,
			output_path: params.output_path,
		},
	};
}

const requestControlledActionTool = defineTool({
	name: "request_controlled_action",
	label: "Request Controlled Action",
	description: "Queue a strict host-executed action for approval and deterministic execution.",
	promptSnippet:
		"request_controlled_action queues a validated host-controlled action. Use run_mock, run_real, or publish_results with normalized guest paths only.",
	promptGuidelines: [
		"Use run_mock for mock-data execution, run_real for real-data execution, and publish_results to release an approved output.",
		"Only pass normalized guest paths under /input, /output, /results, or /release.",
		"Do not invent host paths, shell commands, raw URLs, or extra fields.",
	],
	parameters: RequestControlledActionParams,

	async execute(_toolCallId, params) {
		const payload = controlledActionPayload(params);
		await mkdir(`${controlRoot()}/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 the controlled action inbox and returns pending, 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?: { status?: string; run_id?: string; stdout_path?: string; error?: string };
			};
			const status = typeof parsed.status?.status === "string" ? parsed.status.status : "pending";
			const outputPath = typeof parsed.status?.stdout_path === "string" ? parsed.status.stdout_path : "";
			const error = typeof parsed.status?.error === "string" ? parsed.status.error : "";
			return {
				content: [
					{
						type: "text",
						text: error
							? `Controlled action ${params.action_id}: ${status} (${error})`
							: `Controlled action ${params.action_id}: ${status}`,
					},
				],
				details: {
					action_id: params.action_id,
					status,
					output_path: outputPath,
					error,
					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);
}
