import { environment, jwt } from "@config";
import { redis } from "bun";
import { createDecoder, createSigner, createVerifier } from "fast-jwt";

const signer = createSigner({ key: jwt.secret, expiresIn: jwt.expiration });
const verifier = createVerifier({ key: jwt.secret });
const decoder = createDecoder();

export async function createSession(
	payload: UserSession,
	userAgent: string,
): Promise<string> {
	const token = signer(payload);
	const sessionKey = `session:${payload.id}:${token}`;
	await redis.set(sessionKey, JSON.stringify({ ...payload, userAgent }));
	await redis.expire(sessionKey, getExpirationInSeconds());
	return generateCookie(token);
}

export async function getSession(
	request: Request,
): Promise<UserSession | null> {
	const token = extractToken(request);
	if (!token) return null;
	const keys = await redis.keys(`session:*:${token}`);
	if (!keys.length) return null;
	const raw = await redis.get(keys[0]);
	return raw ? JSON.parse(raw) : null;
}

export async function updateSession(
	request: Request,
	payload: UserSession,
	userAgent: string,
): Promise<string> {
	const token = extractToken(request);
	if (!token) throw new Error("Session token not found");
	const keys = await redis.keys(`session:*:${token}`);
	if (!keys.length) throw new Error("Session not found or expired");
	await redis.set(keys[0], JSON.stringify({ ...payload, userAgent }));
	await redis.expire(keys[0], getExpirationInSeconds());
	return generateCookie(token);
}

export async function verifySession(token: string): Promise<UserSession> {
	const keys = await redis.keys(`session:*:${token}`);
	if (!keys.length) throw new Error("Session not found or expired");
	return verifier(token);
}

export async function decodeSession(token: string): Promise<UserSession> {
	return decoder(token);
}

export async function invalidateSession(request: Request): Promise<void> {
	const token = extractToken(request);
	if (!token) return;
	const keys = await redis.keys(`session:*:${token}`);
	if (!keys.length) return;
	await redis.del(keys[0]);
}

export async function invalidateSessionById(
	sessionId: string,
): Promise<boolean> {
	const keys = await redis.keys(`session:*:${sessionId}`);
	if (!keys.length) return false;
	await redis.del(keys[0]);
	return true;
}

export async function invalidateAllSessionsForUser(
	userId: string,
): Promise<number> {
	const keys = await redis.keys(`session:${userId}:*`);
	if (keys.length === 0) return 0;

	for (const key of keys) {
		await redis.del(key);
	}

	return keys.length;
}

// helpers
function extractToken(request: Request): string | null {
	return request.headers.get("Cookie")?.match(/session=([^;]+)/)?.[1] || null;
}

function generateCookie(
	token: string,
	maxAge = getExpirationInSeconds(),
	options?: {
		secure?: boolean;
		httpOnly?: boolean;
		sameSite?: "Strict" | "Lax" | "None";
		path?: string;
		domain?: string;
	},
): string {
	const {
		secure = !environment.development,
		httpOnly = true,
		sameSite = environment.development ? "Lax" : "None",
		path = "/",
		domain,
	} = options || {};

	let cookie = `session=${encodeURIComponent(token)}; Path=${path}; Max-Age=${maxAge}`;
	if (httpOnly) cookie += "; HttpOnly";
	if (secure) cookie += "; Secure";
	if (sameSite) cookie += `; SameSite=${sameSite}`;
	if (domain) cookie += `; Domain=${domain}`;
	return cookie;
}

function getExpirationInSeconds(): number {
	const match = jwt.expiration.match(/^(\d+)([smhd])$/);
	if (!match) throw new Error("Invalid expiresIn format in jwt config");
	const [, value, unit] = match;
	const num = Number(value);
	switch (unit) {
		case "s":
			return num;
		case "m":
			return num * 60;
		case "h":
			return num * 3600;
		case "d":
			return num * 86400;
		default:
			throw new Error("Invalid time unit in expiresIn");
	}
}

export const sessionManager = {
	createSession,
	getSession,
	updateSession,
	verifySession,
	decodeSession,
	invalidateSession,
	invalidateSessionById,
	invalidateAllSessionsForUser,
};