import { randomBytes } from "node:crypto";
import { and, eq, gt, isNotNull, isNull, or } from "drizzle-orm";
import type { db as prodDb } from "../../db/index.ts";
import { setups, shares } from "../../db/schema.ts";

type Db = typeof prodDb;

/**
 * Create a share link for a setup.
 * Returns null if the setup doesn't belong to the user.
 */
export async function createShareLink(
	db: Db,
	userId: number,
	setupId: number,
	options: { expiresInDays: number | null },
) {
	// Verify ownership
	const [setup] = await db
		.select({ id: setups.id })
		.from(setups)
		.where(and(eq(setups.id, setupId), eq(setups.userId, userId)));
	if (!setup) return null;

	const token = randomBytes(16).toString("base64url");
	const expiresAt = options.expiresInDays
		? new Date(Date.now() + options.expiresInDays * 86400000)
		: null;

	const [row] = await db
		.insert(shares)
		.values({
			setupId,
			token,
			permission: "read",
			expiresAt,
		})
		.returning();

	return row;
}

/**
 * List all share links for a setup owned by the user.
 * Returns links ordered by createdAt desc.
 */
export async function getShareLinks(db: Db, userId: number, setupId: number) {
	// Verify ownership
	const [setup] = await db
		.select({ id: setups.id })
		.from(setups)
		.where(and(eq(setups.id, setupId), eq(setups.userId, userId)));
	if (!setup) return null;

	return db
		.select()
		.from(shares)
		.where(eq(shares.setupId, setupId))
		.orderBy(shares.createdAt);
}

/**
 * Revoke a share link. Returns null if the share doesn't belong to the user's setup.
 */
export async function revokeShareLink(db: Db, userId: number, shareId: number) {
	// Join shares with setups to verify ownership
	const [existing] = await db
		.select({ shareId: shares.id, setupId: shares.setupId })
		.from(shares)
		.innerJoin(setups, eq(shares.setupId, setups.id))
		.where(and(eq(shares.id, shareId), eq(setups.userId, userId)));

	if (!existing) return null;

	const [row] = await db
		.update(shares)
		.set({ revokedAt: new Date() })
		.where(eq(shares.id, shareId))
		.returning();

	return row;
}

/**
 * Validate a share token. Returns { setupId, permission } if valid, null otherwise.
 * Invalid = nonexistent, revoked, or expired.
 */
export async function validateShareToken(db: Db, token: string) {
	const [row] = await db
		.select({
			setupId: shares.setupId,
			permission: shares.permission,
			expiresAt: shares.expiresAt,
		})
		.from(shares)
		.where(and(eq(shares.token, token), isNull(shares.revokedAt)));

	if (!row) return null;

	// Check expiration
	if (row.expiresAt && row.expiresAt < new Date()) return null;

	return { setupId: row.setupId, permission: row.permission };
}

/**
 * Deactivate all active share links for a setup.
 * Called when visibility transitions to "private".
 */
export async function deactivateShareLinks(db: Db, setupId: number) {
	await db
		.update(shares)
		.set({ revokedAt: new Date() })
		.where(and(eq(shares.setupId, setupId), isNull(shares.revokedAt)));
}

/**
 * Reactivate share links for a setup.
 * Called when visibility transitions from "private" back to "link" or "public".
 * Clears revokedAt on all non-expired shares for the setup.
 */
export async function reactivateShareLinks(db: Db, setupId: number) {
	// Per D-03: clear revokedAt on all non-expired shares for the setup.
	// This reactivates everything including manually revoked — acceptable UX
	// since user explicitly chose to re-enable sharing.
	await db
		.update(shares)
		.set({ revokedAt: null })
		.where(
			and(
				eq(shares.setupId, setupId),
				isNotNull(shares.revokedAt),
				or(isNull(shares.expiresAt), gt(shares.expiresAt, new Date())),
			),
		);
}
