import { spawn } from "node:child_process";
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { cookies } from "next/headers";

const AUTH_STATE_ROOT = path.join(
	/* turbopackIgnore: true */ process.cwd(),
	"../../.port_sessions/accounts",
);
const USERS_PATH = path.join(AUTH_STATE_ROOT, "users.json");
const SESSIONS_PATH = path.join(AUTH_STATE_ROOT, "auth_sessions.json");
const COOKIE_NAME = authCookieName();
const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
const SESSION_REFRESH_INTERVAL_MS = 60 * 60 * 12 * 1000;
const LINUX_ACCOUNT_WORKSPACE_NAME = "zk-agent";

type UserRecord = {
	id: string;
	username: string;
	passwordHash: string;
	createdAt: string;
};

type UsersFile = {
	users: UserRecord[];
};

type SessionsFile = {
	sessions: Record<
		string,
		{ accountId: string; createdAt: string; updatedAt?: string }
	>;
};

export type AccountSession = {
	id: string;
	username: string;
};

export async function registerAccount(username: string, password: string) {
	const cleanUsername = normalizeUsername(username);
	validatePassword(password);
	const usersFile = await readUsers();
	if (usersFile.users.some((user) => user.username === cleanUsername)) {
		throw new Error("账号已存在");
	}

	const account: UserRecord = {
		id: cleanUsername,
		username: cleanUsername,
		passwordHash: hashPassword(password),
		createdAt: new Date().toISOString(),
	};
	usersFile.users.push(account);
	await ensureLinuxAccount(account.id, password);
	await writeUsers(usersFile);
	await createAccountDirectories(account.id);
	return createSession(account);
}

export async function loginAccount(username: string, password: string) {
	const cleanUsername = normalizeUsername(username);
	const usersFile = await readUsers();
	const account = usersFile.users.find(
		(user) => user.username === cleanUsername,
	);
	if (!account || !verifyPassword(password, account.passwordHash)) {
		throw new Error("账号或密码错误");
	}
	await ensureLinuxAccount(account.id, password);
	await createAccountDirectories(account.id);
	return createSession(account);
}

const XIAOMI_EMAIL_RE = /^([\w.-]+)@xiaomi\.com$/i;

export function accountIdFromEmail(email: string): string {
	const match = XIAOMI_EMAIL_RE.exec((email ?? "").trim());
	if (!match) {
		throw new Error("请使用小米邮箱（@xiaomi.com）登录");
	}
	const prefix = match[1].toLowerCase();
	if (!/^[a-z0-9._-]{2,40}$/.test(prefix)) {
		throw new Error(
			"邮箱前缀只能包含小写字母、数字、点、下划线或中划线，长度 2-40",
		);
	}
	return prefix;
}

export async function registerByEmail(email: string, password: string) {
	const accountId = accountIdFromEmail(email);
	validatePassword(password);
	const usersFile = await readUsers();
	if (usersFile.users.some((user) => user.id === accountId)) {
		throw new Error("该邮箱已注册，请直接登录");
	}
	const account: UserRecord = {
		id: accountId,
		username: accountId,
		passwordHash: hashPassword(password),
		createdAt: new Date().toISOString(),
	};
	usersFile.users.push(account);
	await ensureLinuxAccount(account.id, password);
	await writeUsers(usersFile);
	await createAccountDirectories(account.id);
	return createSession(account);
}

export async function loginByEmail(email: string, password: string) {
	const accountId = accountIdFromEmail(email);
	const usersFile = await readUsers();
	const account = usersFile.users.find((user) => user.id === accountId);
	if (!account) {
		throw new Error("账号不存在，请先注册");
	}
	if (!account.passwordHash) {
		throw new Error("该账号尚未设置密码，请联系管理员重置");
	}
	if (!verifyPassword(password, account.passwordHash)) {
		throw new Error("邮箱或密码错误");
	}
	await ensureLinuxAccount(account.id, password);
	await createAccountDirectories(account.id);
	return createSession(account);
}

export async function logoutAccount() {
	const cookieStore = await cookies();
	const token = cookieStore.get(COOKIE_NAME)?.value;
	if (token) {
		const sessionsFile = await readSessions();
		delete sessionsFile.sessions[token];
		await writeSessions(sessionsFile);
	}
	cookieStore.delete(COOKIE_NAME);
}

export async function getCurrentAccount(): Promise<AccountSession | null> {
	const cookieStore = await cookies();
	const token = cookieStore.get(COOKIE_NAME)?.value;
	if (!token) return null;

	const sessionsFile = await readSessions();
	const session = sessionsFile.sessions[token];
	if (!session) return null;

	const usersFile = await readUsers();
	const account = usersFile.users.find((user) => user.id === session.accountId);
	if (!account) return null;
	if (shouldRefreshSession(session.updatedAt ?? session.createdAt)) {
		session.updatedAt = new Date().toISOString();
		await writeSessions(sessionsFile);
		await setSessionCookie(token);
	}
	return { id: account.id, username: account.username };
}

export function accountUploadRoot(accountId: string) {
	return path.join(accountBaseRoot(accountId), "sessions");
}

export function accountOutputRoot(accountId: string) {
	return path.join(accountBaseRoot(accountId), "sessions");
}

export function accountBaseRoot(accountId: string) {
	if (linuxAccountsEnabled()) {
		return path.join("/home", accountId, LINUX_ACCOUNT_WORKSPACE_NAME);
	}
	return path.join(AUTH_STATE_ROOT, accountId);
}

export function accountSessionInputRoot(accountId: string, sessionId: string) {
	return path.join(accountBaseRoot(accountId), "sessions", sessionId, "input");
}

export function accountSessionOutputRoot(accountId: string, sessionId: string) {
	return path.join(accountBaseRoot(accountId), "sessions", sessionId, "output");
}

export function accountSessionScratchpadRoot(
	accountId: string,
	sessionId: string,
) {
	return path.join(
		accountBaseRoot(accountId),
		"sessions",
		sessionId,
		"scratchpad",
	);
}

export function accountSessionRoot(accountId: string, sessionId: string) {
	return path.join(accountBaseRoot(accountId), "sessions", sessionId);
}

async function createSession(account: UserRecord) {
	const token = randomBytes(32).toString("hex");
	const sessionsFile = await readSessions();
	sessionsFile.sessions[token] = {
		accountId: account.id,
		createdAt: new Date().toISOString(),
		updatedAt: new Date().toISOString(),
	};
	await writeSessions(sessionsFile);

	await setSessionCookie(token);
	return { id: account.id, username: account.username };
}

async function setSessionCookie(token: string) {
	const cookieStore = await cookies();
	cookieStore.set(COOKIE_NAME, token, {
		httpOnly: true,
		sameSite: "lax",
		path: "/",
		maxAge: SESSION_MAX_AGE_SECONDS,
	});
}

async function createAccountDirectories(accountId: string) {
	const base = accountBaseRoot(accountId);
	await Promise.all([
		mkdir(path.join(base, "sessions"), { recursive: true }),
		mkdir(path.join(base, "python"), { recursive: true }),
		mkdir(path.join(base, "memory"), { recursive: true }),
		mkdir(path.join(base, "integrations"), { recursive: true }),
	]);
	await chownAccountPath(accountId, base, true);
}

async function readUsers(): Promise<UsersFile> {
	await mkdir(AUTH_STATE_ROOT, { recursive: true });
	try {
		return JSON.parse(await readFile(USERS_PATH, "utf8")) as UsersFile;
	} catch {
		return { users: [] };
	}
}

async function writeUsers(payload: UsersFile) {
	await mkdir(AUTH_STATE_ROOT, { recursive: true });
	await writeFile(USERS_PATH, JSON.stringify(payload, null, 2), "utf8");
}

async function readSessions(): Promise<SessionsFile> {
	await mkdir(AUTH_STATE_ROOT, { recursive: true });
	try {
		return JSON.parse(await readFile(SESSIONS_PATH, "utf8")) as SessionsFile;
	} catch {
		return { sessions: {} };
	}
}

async function writeSessions(payload: SessionsFile) {
	await mkdir(AUTH_STATE_ROOT, { recursive: true });
	await writeFile(SESSIONS_PATH, JSON.stringify(payload, null, 2), "utf8");
}

function normalizeUsername(username: string) {
	const cleanUsername = username.trim().toLowerCase();
	if (linuxAccountsEnabled()) {
		if (!/^[a-z_][a-z0-9_-]{1,31}$/.test(cleanUsername)) {
			throw new Error(
				"启用 Linux 账号时，账号只能包含小写字母、数字、下划线或中划线，且必须以小写字母或下划线开头，长度 2-32",
			);
		}
		return cleanUsername;
	}
	if (!/^[a-z0-9._-]{2,40}$/.test(cleanUsername)) {
		throw new Error(
			"账号只能包含小写字母、数字、点、下划线或中划线，长度 2-40",
		);
	}
	return cleanUsername;
}

function validatePassword(password: string) {
	if (password.length < 6) throw new Error("密码至少 6 位");
}

function hashPassword(password: string) {
	const salt = randomBytes(16).toString("hex");
	const hash = scryptSync(password, salt, 64).toString("hex");
	return `${salt}:${hash}`;
}

function verifyPassword(password: string, passwordHash: string) {
	const [salt, expectedHash] = passwordHash.split(":");
	if (!salt || !expectedHash) return false;
	const actual = Buffer.from(scryptSync(password, salt, 64).toString("hex"));
	const expected = Buffer.from(expectedHash);
	return actual.length === expected.length && timingSafeEqual(actual, expected);
}

function shouldRefreshSession(value?: string) {
	if (!value) return true;
	const timestamp = Date.parse(value);
	if (Number.isNaN(timestamp)) return true;
	return Date.now() - timestamp > SESSION_REFRESH_INTERVAL_MS;
}

function authCookieName() {
	const explicit = sanitizeCookieNamePart(process.env.CLAW_AUTH_COOKIE_NAME);
	if (explicit) return explicit;
	const instance =
		sanitizeCookieNamePart(process.env.CLAW_DEPLOY_INSTANCE) ||
		sanitizeCookieNamePart(process.env.CLAW_SERVICE_SCOPE) ||
		"local";
	const port = sanitizeCookieNamePart(process.env.CLAW_FRONTEND_PORT) || "dev";
	return `claw_account_session_${instance}_${port}`;
}

function sanitizeCookieNamePart(value?: string) {
	const normalized = (value ?? "").trim().toLowerCase();
	if (!normalized) return "";
	return normalized.replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
}

function linuxAccountsEnabled() {
	return ["1", "true", "yes", "on"].includes(
		(process.env.CLAW_ENABLE_LINUX_ACCOUNTS ?? "").trim().toLowerCase(),
	);
}

async function ensureLinuxAccount(accountId: string, password: string) {
	if (!linuxAccountsEnabled()) return;
	if (process.platform !== "linux") {
		throw new Error("CLAW_ENABLE_LINUX_ACCOUNTS=1 只支持 Linux 部署环境");
	}
	if (typeof process.getuid === "function" && process.getuid() !== 0) {
		throw new Error(
			"CLAW_ENABLE_LINUX_ACCOUNTS=1 需要 Web 服务以 root 身份运行",
		);
	}
	await runCommand("id", ["-u", accountId], { allowFailure: true }).then(
		async (result) => {
			if (result.code === 0) return;
			await runCommand("useradd", [
				"-m",
				"-d",
				`/home/${accountId}`,
				"-s",
				"/bin/bash",
				accountId,
			]);
		},
	);
	await runCommand("chpasswd", [], {
		input: `${accountId}:${password}\n`,
	});
}

export async function chownAccountPath(
	accountId: string,
	targetPath: string,
	recursive = false,
) {
	if (!linuxAccountsEnabled()) return;
	await runCommand("chown", [
		...(recursive ? ["-R"] : []),
		accountId,
		targetPath,
	]);
}

function runCommand(
	command: string,
	args: string[],
	options: { input?: string; allowFailure?: boolean } = {},
) {
	return new Promise<{ code: number; stdout: string; stderr: string }>(
		(resolve, reject) => {
			const child = spawn(command, args, {
				stdio: ["pipe", "pipe", "pipe"],
			});
			let stdout = "";
			let stderr = "";
			child.stdout.on("data", (chunk) => {
				stdout += chunk.toString();
			});
			child.stderr.on("data", (chunk) => {
				stderr += chunk.toString();
			});
			child.on("error", reject);
			child.on("close", (code) => {
				const exitCode = code ?? 1;
				if (exitCode !== 0 && !options.allowFailure) {
					reject(
						new Error(
							`${command} ${args.join(" ")} 失败：${stderr || stdout || exitCode}`,
						),
					);
					return;
				}
				resolve({ code: exitCode, stdout, stderr });
			});
			if (options.input) child.stdin.write(options.input);
			child.stdin.end();
		},
	);
}
