import { execFileSync } from "node:child_process";
import { readFileSync, watchFile, unwatchFile } from "node:fs";
import { join, relative, resolve, sep } from "node:path";
import type { ExtensionAPI, ExtensionContext, ToolCallEvent } from "@earendil-works/pi-coding-agent";

const STATUS_FILE = "work/runtime-status.json";
const MATRIX_FILE = "harness/agents.matrix.yml";
const ARTIFACTS_DIR = "work/artifacts";
const WIDGET_KEY = "orquestra-runtime";
const STATUS_KEY = "orquestra-runtime";
const STATUS_GAP = "   ";

const DEFAULT_EMOJIS: Record<string, string> = {
	leader: "🧭",
	architect: "🏗️",
	implementer: "🛠️",
	reviewer: "🔍",
	security: "🔒",
	qa: "🧪",
	documenter: "📝",
};

const GATE_FILES: Record<string, string> = {
	reviewer: "reviewer.json",
	security: "security.json",
	qa: "qa.json",
	leader: "leader-close.json",
};

const AGENT_FILES: Record<string, string> = {
	architect: "architect.md",
	implementer: "implementer.md",
	reviewer: "reviewer.json",
	security: "security.json",
	qa: "qa.json",
	documenter: "documenter.md",
	leader: "leader-close.json",
};

type RuntimeStatus = {
	feature_id?: string | null;
	stage?: string;
	agent?: string;
	action?: string;
	state?: string;
	next_agent?: string;
	waiting_for?: string;
	updated_at?: string;
	timeline?: Array<{ ts?: string; agent?: string; stage?: string; state?: string; message?: string }>;
};

const ROOT_PRODUCT_EXTENSIONS = new Set([".py", ".js", ".ts", ".go", ".rs", ".java", ".php", ".rb"]);

// Carpetas permitidas para escritura (Orquestra + proyecto)
const ALLOWED_WRITE_DIRS = new Set([
	"project", // código del proyecto
	"tests", // tests del proyecto
	"work", // artifacts, current.md, runtime-status.json
	"backlog", // features.json
	"spec", // specs globales
	"harness", // configuración Orquestra (solo leader)
	"scripts", // scripts Orquestra (solo leader)
	"platforms", // extensiones Pi (solo leader)
	"docs", // documentación (solo leader)
]);

function safeReadJson(root: string, path: string): RuntimeStatus | null {
	try {
		return JSON.parse(readFileSync(join(root, path), "utf8")) as RuntimeStatus;
	} catch {
		return null;
	}
}

function loadRoleEmojis(root: string): Record<string, string> {
	try {
		const text = readFileSync(join(root, MATRIX_FILE), "utf8");
		const emojis = { ...DEFAULT_EMOJIS };
		let currentRole: string | null = null;
		for (const line of text.split(/\r?\n/)) {
			const roleMatch = line.match(/^  ([a-z_]+):\s*$/);
			if (roleMatch) {
				currentRole = roleMatch[1];
				continue;
			}
			const emojiMatch = line.match(/^\s{4}emoji:\s*["']?(.*?)["']?\s*$/);
			if (emojiMatch && currentRole) emojis[currentRole] = emojiMatch[1];
		}
		return emojis;
	} catch {
		return { ...DEFAULT_EMOJIS };
	}
}

function gateState(root: string, featureId?: string | null): Record<string, string> {
	const result: Record<string, string> = {};
	if (!featureId) return result;
	for (const [gate, filename] of Object.entries(GATE_FILES)) {
		try {
			if (filename.endsWith(".md")) {
				readFileSync(join(root, ARTIFACTS_DIR, featureId, filename), "utf8");
				result[gate] = "APPROVED";
				continue;
			}
			const json = JSON.parse(readFileSync(join(root, ARTIFACTS_DIR, featureId, filename), "utf8")) as { verdict?: string };
			result[gate] = json.verdict === "APPROVED" ? "APPROVED" : "PRESENT";
		} catch {
			result[gate] = "PENDING";
		}
	}
	return result;
}

function gateSummary(root: string, featureId: string | null | undefined, emojis: Record<string, string>): string {
	const gates = gateState(root, featureId);
	if (!featureId) return "Gates: —";
	return `Gates: ${["reviewer", "security", "qa", "leader"]
		.map((gate) => `${emojis[gate] || "•"} ${gates[gate] === "APPROVED" ? "✅" : gates[gate] === "PRESENT" ? "⚠️" : "⏳"}`)
		.join(STATUS_GAP)}`;
}

function artifactStatus(root: string, featureId: string | null | undefined, agent: string): string {
	if (!featureId) return "PENDING";
	const filename = AGENT_FILES[agent];
	if (!filename) return "PENDING";
	try {
		if (filename.endsWith(".md")) {
			readFileSync(join(root, ARTIFACTS_DIR, featureId, filename), "utf8");
			return "DONE";
		}
		const json = JSON.parse(readFileSync(join(root, ARTIFACTS_DIR, featureId, filename), "utf8")) as { verdict?: string };
		return json.verdict === "APPROVED" ? "DONE" : "PRESENT";
	} catch {
		return "PENDING";
	}
}

function agentSummary(root: string, status: RuntimeStatus, emojis: Record<string, string>): string {
	const featureId = status.feature_id || null;
	const currentAgent = status.agent || "leader";
	return `Roles: ${["architect", "implementer", "reviewer", "security", "qa", "documenter", "leader"]
		.map((agent) => {
			const state = artifactStatus(root, featureId, agent);
			const mark = state === "DONE" ? "✅" : state === "PRESENT" ? "⚠️" : agent === currentAgent ? "▶️" : "⏳";
			return `${emojis[agent] || "•"} ${mark}`;
		})
		.join(STATUS_GAP)}`;
}

function isInsideRel(relPath: string, dirname: string): boolean {
	return relPath === dirname || relPath.startsWith(`${dirname}/`) || relPath.startsWith(`${dirname}${sep}`);
}

function normalizeWritePath(root: string, rawPath: unknown): { relPath?: string; reason?: string } {
	if (typeof rawPath !== "string" || rawPath.trim() === "") {
		return { reason: "Orquestra bloqueó una escritura sin path válido." };
	}

	const absPath = resolve(root, rawPath);
	const relPath = relative(root, absPath);
	if (relPath === "" || relPath.startsWith("..") || resolve(root, relPath) !== absPath) {
		return { reason: "Orquestra bloqueó una escritura fuera de la raíz del proyecto." };
	}
	return { relPath: relPath.replace(/\\/g, "/") };
}

function isRootProductFile(relPath: string): boolean {
	if (relPath.includes("/")) return false;
	const dot = relPath.lastIndexOf(".");
	return dot > 0 && ROOT_PRODUCT_EXTENSIONS.has(relPath.slice(dot));
}

function hasActiveBuildRuntime(status: RuntimeStatus | null): boolean {
	return Boolean(
		status?.feature_id &&
		status.stage === "build" &&
		status.agent === "implementer" &&
		status.state === "running",
	);
}

function parseStageCommand(args: string, status: RuntimeStatus | null): { stage?: string; featureId?: string; error?: string } {
	const parts = args.trim().split(/\s+/).filter(Boolean);
	const stage = parts[0];
	const featureFlag = parts.indexOf("--feature-id");
	const featureId = featureFlag >= 0 ? parts[featureFlag + 1] : parts[1] || status?.feature_id || undefined;
	if (!stage) return { error: "Uso: /orquestra-stage <stage> [feature_id]" };
	if (!featureId) return { error: "Falta feature_id. Uso: /orquestra-stage <stage> <feature_id>" };
	return { stage, featureId };
}

function lastUsefulLine(text: string): string {
	return text
		.split(/\r?\n/)
		.map((line) => line.trim())
		.filter(Boolean)
		.slice(-1)[0] || "stage terminado";
}

function guardWriteOrEdit(event: ToolCallEvent, ctx: ExtensionContext): { block?: boolean; reason?: string } | undefined {
	if (event.toolName !== "write" && event.toolName !== "edit") return undefined;

	const { relPath, reason } = normalizeWritePath(ctx.cwd, (event.input as { path?: unknown }).path);
	if (!relPath) return { block: true, reason };

	// Extraer el directorio raíz del path
	const slashIndex = relPath.indexOf("/");
	const rootDir = slashIndex === -1 ? relPath : relPath.slice(0, slashIndex);

	// Bloquear escrituras fuera de carpetas permitidas
	if (!ALLOWED_WRITE_DIRS.has(rootDir)) {
		return {
			block: true,
			reason: `Orquestra bloqueó '${relPath}': solo se permite escribir en ${Array.from(ALLOWED_WRITE_DIRS).join(", ")}. No crees carpetas ni archivos en la raíz ni en ubicaciones no autorizadas.`,
		};
	}

	// Bloquear escrituras directas a backlog/features.json (debe usar close_feature.py)
	if (relPath === "backlog/features.json") {
		return {
			block: true,
			reason: `Orquestra bloqueó '${relPath}': NUNCA edites backlog/features.json directamente. Usa scripts/close_feature.py para cerrar features. Este script valida gates y actualiza el backlog automáticamente.`,
		};
	}

	if (isRootProductFile(relPath)) {
		return {
			block: true,
			reason: `Orquestra bloqueó '${relPath}': los archivos de producto/código no van en la raíz. Escribí ese código dentro de project/.`,
		};
	}

	if (isInsideRel(relPath, "project") || isInsideRel(relPath, "tests")) {
		const status = safeReadJson(ctx.cwd, STATUS_FILE);
		if (!hasActiveBuildRuntime(status)) {
			return {
				block: true,
				reason:
					"Orquestra bloqueó esta escritura: los cambios en project/ o tests/ requieren una feature/ticket activa, stage build, agent implementer y state running. Seleccioná o creá un ticket y actualizá work/runtime-status.json con scripts/agent_status.py antes de escribir.",
			};
		}
	}

	return undefined;
}

function render(root: string, status: RuntimeStatus | null, emojis: Record<string, string>) {
	if (!status) {
		return {
			widget: ["Orquestra: no se pudo leer work/runtime-status.json"],
			footer: "Orquestra status unavailable",
		};
	}

	const feature = status.feature_id || "—";
	const stage = status.stage || "—";
	const agent = status.agent || "leader";
	const action = status.action || "—";
	const state = status.state || "—";
	const nextAgent = status.next_agent || "—";
	const waitingFor = status.waiting_for || "—";
	const updatedAt = status.updated_at || "—";
	const lastEvent = status.timeline && status.timeline.length > 0 ? status.timeline[status.timeline.length - 1] : null;
	const lastLine = lastEvent
		? `Último: ${lastEvent.ts || "—"} · ${emojis[lastEvent.agent || "leader"] || "•"} ${lastEvent.message || "—"}`
		: "Último: —";

	return {
		widget: [
			`Orquestra ${emojis[agent] || "•"} ${feature} · ${stage} · ${agent} · ${state}`,
			`Acción: ${action}`,
			`Siguiente: ${emojis[nextAgent] || "•"} ${nextAgent} · Esperando: ${waitingFor}`,
			agentSummary(root, status, emojis),
			gateSummary(root, status.feature_id || null, emojis),
			lastLine,
			`Actualizado: ${updatedAt}`,
		],
		footer: `Orquestra ${emojis[agent] || "•"} ${feature} · ${stage} · ${state}`,
	};
}

export default function orquestraStatusExtension(pi: ExtensionAPI) {
	let currentCtx: ExtensionContext | null = null;
	let watching = false;
	let lastRendered = "";

	function apply(ctx: ExtensionContext) {
		const root = ctx.cwd;
		const emojis = loadRoleEmojis(root);
		const rendered = render(root, safeReadJson(root, STATUS_FILE), emojis);
		const signature = JSON.stringify(rendered);
		if (signature === lastRendered) return;
		lastRendered = signature;
		ctx.ui.setWidget(WIDGET_KEY, rendered.widget);
		ctx.ui.setStatus(STATUS_KEY, rendered.footer);
	}

	function refresh() {
		if (!currentCtx || !currentCtx.hasUI) return;
		apply(currentCtx);
	}

	function startWatching() {
		if (watching) return;
		if (!currentCtx) return;
		watchFile(join(currentCtx.cwd, STATUS_FILE), { interval: 1000, persistent: false }, () => refresh());
		watching = true;
	}

	function stopWatching() {
		if (!watching) return;
		if (currentCtx) unwatchFile(join(currentCtx.cwd, STATUS_FILE));
		watching = false;
	}

	pi.on("session_start", async (_event, ctx) => {
		if (!ctx.hasUI) return;
		currentCtx = ctx;
		apply(ctx);
		startWatching();
	});

	pi.on("session_shutdown", async (_event, ctx) => {
		ctx.ui.setWidget(WIDGET_KEY, undefined);
		ctx.ui.setStatus(STATUS_KEY, undefined);
		stopWatching();
		currentCtx = null;
	});

	pi.on("tool_call", async (event, ctx) => guardWriteOrEdit(event, ctx));

	pi.registerCommand("orquestra-status", {
		description: "Refresca el widget de runtime status de Orquestra",
		handler: async (_args, ctx) => {
			currentCtx = ctx;
			apply(ctx);
			ctx.ui.notify("Orquestra status refrescado", "info");
		},
	});

	pi.registerCommand("orquestra-stage", {
		description: "Ejecuta un stage Orquestra en un proceso Pi fresco: /orquestra-stage <stage> [feature_id]",
		handler: async (args, ctx) => {
			const parsed = parseStageCommand(args, safeReadJson(ctx.cwd, STATUS_FILE));
			if (parsed.error) {
				ctx.ui.notify(parsed.error, "error");
				return;
			}

			ctx.ui.notify(`Orquestra ejecutando ${parsed.stage} en Pi fresco…`, "info");
			try {
				const output = execFileSync("python3", ["scripts/run_stage.py", parsed.stage!, "--feature-id", parsed.featureId!], {
					cwd: ctx.cwd,
					encoding: "utf8",
					maxBuffer: 1024 * 1024,
					stdio: ["ignore", "pipe", "pipe"],
				});
				currentCtx = ctx;
				apply(ctx);
				ctx.ui.notify(`Orquestra stage terminado: ${lastUsefulLine(output)}`, "info");
			} catch (error) {
				const failed = error as { stdout?: string; stderr?: string; message?: string };
				const detail = lastUsefulLine(`${failed.stdout || ""}\n${failed.stderr || ""}`) || failed.message || "error desconocido";
				currentCtx = ctx;
				apply(ctx);
				ctx.ui.notify(`Orquestra stage falló: ${detail}`, "error");
			}
		},
	});
}
