import { randomBytes } from "node:crypto";
import { eq } from "drizzle-orm";
import type { Context } from "hono";
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
import type { Database } from "./db/client.js";
import { extensionTokens, sessions, users } from "./db/schema.js";
import type { Env } from "./env.js";

export const SESSION_COOKIE = "circuy_session";
export const EXT_REDIRECT_COOKIE = "circuy_ext_redirect";
const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;

export type Actor = {
	userId: string;
	organizationId: string;
	email: string;
};

function cookieOptions(env: Env) {
	return {
		httpOnly: true,
		sameSite: "Lax" as const,
		secure: env.cookieSecure,
		path: "/",
		maxAge: Math.floor(SESSION_TTL_MS / 1000),
	};
}

export function isAllowedExtensionRedirect(uri: string): boolean {
	try {
		const url = new URL(uri);
		if (url.protocol === "chrome-extension:") return true;
		return (
			url.protocol === "https:" && url.hostname.endsWith(".chromiumapp.org")
		);
	} catch {
		return false;
	}
}

export async function createSession(
	db: Database,
	c: Context,
	env: Env,
	userId: string,
): Promise<string> {
	const id = randomBytes(32).toString("base64url");
	await db.insert(sessions).values({
		id,
		userId,
		expiresAt: new Date(Date.now() + SESSION_TTL_MS),
	});
	setCookie(c, SESSION_COOKIE, id, cookieOptions(env));
	return id;
}

export async function createExtensionToken(
	db: Database,
	userId: string,
	label?: string,
): Promise<string> {
	const token = randomBytes(32).toString("base64url");
	await db.insert(extensionTokens).values({ token, userId, label });
	return token;
}

export async function destroySession(
	db: Database,
	c: Context,
	env: Env,
): Promise<void> {
	const id = getCookie(c, SESSION_COOKIE);
	if (id) {
		await db.delete(sessions).where(eq(sessions.id, id));
	}
	deleteCookie(c, SESSION_COOKIE, {
		path: "/",
		secure: env.cookieSecure,
	});
}

export async function loadActor(
	db: Database,
	c: Context,
): Promise<Actor | undefined> {
	const header = c.req.header("authorization");
	if (header?.startsWith("Bearer ")) {
		return loadBearerActor(db, header.slice("Bearer ".length).trim());
	}

	const id = getCookie(c, SESSION_COOKIE);
	if (!id) return undefined;

	const rows = await db
		.select({
			sessionId: sessions.id,
			expiresAt: sessions.expiresAt,
			userId: users.id,
			organizationId: users.organizationId,
			email: users.email,
		})
		.from(sessions)
		.innerJoin(users, eq(sessions.userId, users.id))
		.where(eq(sessions.id, id))
		.limit(1);

	const row = rows[0];
	if (!row) return undefined;
	if (row.expiresAt.getTime() <= Date.now()) {
		await db.delete(sessions).where(eq(sessions.id, id));
		return undefined;
	}

	await db
		.update(sessions)
		.set({ expiresAt: new Date(Date.now() + SESSION_TTL_MS) })
		.where(eq(sessions.id, row.sessionId));

	return {
		userId: row.userId,
		organizationId: row.organizationId,
		email: row.email,
	};
}

async function loadBearerActor(
	db: Database,
	token: string,
): Promise<Actor | undefined> {
	if (!token) return undefined;
	const rows = await db
		.select({
			tokenId: extensionTokens.id,
			userId: users.id,
			organizationId: users.organizationId,
			email: users.email,
		})
		.from(extensionTokens)
		.innerJoin(users, eq(extensionTokens.userId, users.id))
		.where(eq(extensionTokens.token, token))
		.limit(1);
	const row = rows[0];
	if (!row) return undefined;
	await db
		.update(extensionTokens)
		.set({ lastUsedAt: new Date() })
		.where(eq(extensionTokens.id, row.tokenId));
	return {
		userId: row.userId,
		organizationId: row.organizationId,
		email: row.email,
	};
}
