import { readdirSync, readFileSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";

import {
	collectRecentLogEvents,
	formatMergedLogEvent,
	getProjectSessionKey,
} from "./cli-logs.js";
import type { QuerySnapshot } from "./headless-query.js";

interface StatusArgs {
	watch: boolean;
}

interface StatusDeps {
	basePath: string;
	sfHome?: string;
	stdout?: Pick<typeof process.stdout, "write" | "isTTY">;
	stderr?: Pick<typeof process.stderr, "write">;
}

interface CurrentModel {
	provider: string;
	model: string;
}

function parseStatusArgs(argv: string[]): StatusArgs {
	const args = argv.slice(1);
	return {
		watch: args.includes("--watch"),
	};
}

function formatRef(
	ref: { id: string; title?: string } | null | undefined,
): string {
	if (!ref) return "n/a";
	return ref.title ? `${ref.id} ${ref.title}` : ref.id;
}

function formatDispatch(snapshot: QuerySnapshot): string {
	const next = snapshot.next;
	if (next.action === "dispatch") {
		return `${next.unitType ?? "unit"} ${next.unitId ?? "n/a"}`;
	}
	if (next.action === "stop")
		return `stop: ${next.reason ?? snapshot.state.nextAction}`;
	return `skip: ${next.reason ?? snapshot.state.nextAction}`;
}

function formatCost(snapshot: QuerySnapshot): string {
	const total = snapshot.cost.total;
	const workers = snapshot.cost.workers.length;
	return `$${total.toFixed(4)}${workers > 0 ? ` (${workers} worker${workers === 1 ? "" : "s"})` : ""}`;
}

function latestJsonlFile(dir: string): string | null {
	try {
		const entries = readdirSync(dir)
			.filter((file) => file.endsWith(".jsonl"))
			.map((file) => {
				const path = join(dir, file);
				try {
					return { path, mtimeMs: statSync(path).mtimeMs };
				} catch {
					return null;
				}
			})
			.filter(
				(entry): entry is { path: string; mtimeMs: number } => entry !== null,
			);
		entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
		return entries[0]?.path ?? null;
	} catch {
		return null;
	}
}

function extractCurrentModelFromSession(filePath: string): CurrentModel | null {
	let current: CurrentModel | null = null;
	let raw: string;
	try {
		raw = readFileSync(filePath, "utf-8");
	} catch {
		return null;
	}

	for (const line of raw.split(/\n/)) {
		if (!line.trim()) continue;
		let entry: Record<string, unknown>;
		try {
			entry = JSON.parse(line) as Record<string, unknown>;
		} catch {
			continue;
		}

		if (entry.type === "model_change") {
			const provider = String(entry.provider ?? "");
			const model = String(entry.modelId ?? "");
			if (provider || model) current = { provider, model };
			continue;
		}

		if (entry.type === "message") {
			const message = entry.message as Record<string, unknown> | undefined;
			if (message?.role === "assistant") {
				const provider = String(message.provider ?? "");
				const model = String(message.model ?? "");
				if (provider || model) current = { provider, model };
			}
		}
	}

	return current;
}

function getCurrentModel(
	basePath: string,
	sfHome: string,
): CurrentModel | null {
	const key = getProjectSessionKey(basePath);
	const sessionFile =
		latestJsonlFile(join(sfHome, "agent", "sessions", key)) ??
		latestJsonlFile(join(sfHome, "sessions", key));
	return sessionFile ? extractCurrentModelFromSession(sessionFile) : null;
}

function formatModel(model: CurrentModel | null): string {
	if (!model) return "n/a";
	if (model.provider && model.model) return `${model.provider}/${model.model}`;
	return model.model || model.provider || "n/a";
}

export function renderLiveStatus(
	snapshot: QuerySnapshot,
	opts: { model: CurrentModel | null; recentEvents: string[] },
): string {
	const lines: string[] = [];
	lines.push("SF Status");
	lines.push("---------");
	lines.push(
		`Milestone: ${formatRef(snapshot.state.activeMilestone ?? snapshot.state.lastCompletedMilestone)}`,
	);
	lines.push(`Slice:     ${formatRef(snapshot.state.activeSlice)}`);
	lines.push(`Task:      ${formatRef(snapshot.state.activeTask)}`);
	lines.push(`Phase:     ${snapshot.state.phase}`);
	lines.push(`Dispatch:  ${formatDispatch(snapshot)}`);
	lines.push(`Cost:      ${formatCost(snapshot)}`);
	lines.push(`Model:     ${formatModel(opts.model)}`);
	lines.push("");
	lines.push("Last Events:");
	if (opts.recentEvents.length === 0) {
		lines.push("  n/a");
	} else {
		for (const event of opts.recentEvents) {
			lines.push(`  ${event.trimEnd()}`);
		}
	}
	return lines.join("\n") + "\n";
}

async function buildStatusText(
	basePath: string,
	sfHome: string,
): Promise<string> {
	const { buildQuerySnapshot } = await import("./headless-query.js");
	const snapshot = await buildQuerySnapshot(basePath);
	const recentEvents = collectRecentLogEvents({
		basePath,
		sfHome,
		limit: 50,
	})
		.filter((event) => event.source === "notif" || event.source === "activity")
		.slice(-10)
		.map(formatMergedLogEvent);

	return renderLiveStatus(snapshot, {
		model: getCurrentModel(basePath, sfHome),
		recentEvents,
	});
}

export async function runStatusCli(
	argv: string[],
	deps: StatusDeps,
): Promise<number> {
	const stdout = deps.stdout ?? process.stdout;
	const stderr = deps.stderr ?? process.stderr;
	const sfHome = deps.sfHome ?? process.env.SF_HOME ?? join(homedir(), ".sf");
	const args = parseStatusArgs(argv);

	const renderOnce = async () => {
		try {
			const text = await buildStatusText(deps.basePath, sfHome);
			if (args.watch && stdout.isTTY) stdout.write("\x1b[2J\x1b[H");
			stdout.write(text);
		} catch (err) {
			stderr.write(
				`sf status: ${err instanceof Error ? err.message : String(err)}\n`,
			);
			throw err;
		}
	};

	if (!args.watch) {
		await renderOnce();
		return 0;
	}

	await renderOnce();
	await new Promise<void>((resolve) => {
		const timer = setInterval(() => {
			renderOnce().catch(() => {
				clearInterval(timer);
				resolve();
			});
		}, 2000);
		const stop = () => {
			clearInterval(timer);
			process.off("SIGINT", stop);
			process.off("SIGTERM", stop);
			resolve();
		};
		process.on("SIGINT", stop);
		process.on("SIGTERM", stop);
	});
	return 0;
}
