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_ROOT = path.join(
	/* turbopackIgnore: true */ process.cwd(),
	"../../.port_sessions/accounts",
);
const USERS_PATH = path.join(AUTH_ROOT, "users.json");
const SESSIONS_PATH = path.join(AUTH_ROOT, "auth_sessions.json");
const COOKIE_NAME = "claw_account_session";
const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
const SESSION_REFRESH_INTERVAL_MS = 60 * 60 * 12 * 1000;

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 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 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(AUTH_ROOT, accountId, "sessions");
}

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

export function accountBaseRoot(accountId: string) {
	return path.join(AUTH_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) {
	await Promise.all(
		["sessions"].map((name) =>
			mkdir(path.join(AUTH_ROOT, accountId, name), { recursive: true }),
		),
	);
}

async function readUsers(): Promise<UsersFile> {
	await mkdir(AUTH_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_ROOT, { recursive: true });
	await writeFile(USERS_PATH, JSON.stringify(payload, null, 2), "utf8");
}

async function readSessions(): Promise<SessionsFile> {
	await mkdir(AUTH_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_ROOT, { recursive: true });
	await writeFile(SESSIONS_PATH, JSON.stringify(payload, null, 2), "utf8");
}

function normalizeUsername(username: string) {
	const cleanUsername = username.trim().toLowerCase();
	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;
}
