import { eq, and, lt, sql } from 'drizzle-orm';
import { db } from '../db';
import { users, passkeys, passkeyChallenges, tokenBlacklist } from '../schema';
import type { UserSelect, UserInsert, PasskeySelect, PasskeyInsert } from '../types';

// ── Users ──

export async function getUsers(): Promise<UserSelect[]> {
  return db.select().from(users);
}

export async function getUserById(id: number): Promise<UserSelect | undefined> {
  const [user] = await db.select().from(users).where(eq(users.id, id));
  return user;
}

export async function getUserByEmail(email: string): Promise<UserSelect | undefined> {
  const [user] = await db.select().from(users).where(eq(users.email, email));
  return user;
}

export async function getUserCount(): Promise<number> {
  const [result] = await db.select({ count: sql<number>`count(*)::int` }).from(users);
  return result?.count ?? 0;
}

export async function createUser(data: UserInsert): Promise<UserSelect> {
  const [user] = await db.insert(users).values(data).returning();
  return user!;
}

export async function updateUser(id: number, data: Partial<Omit<UserSelect, 'id'>>): Promise<UserSelect | undefined> {
  const [user] = await db.update(users).set({ ...data, updatedAt: new Date() }).where(eq(users.id, id)).returning();
  return user;
}

export async function deleteUser(id: number): Promise<boolean> {
  const result = await db.delete(users).where(eq(users.id, id)).returning({ id: users.id });
  return result.length > 0;
}

// ── Passkeys ──

export async function getPasskeysByUserId(userId: number): Promise<PasskeySelect[]> {
  return db.select().from(passkeys).where(eq(passkeys.userId, userId));
}

export async function getPasskeysByUserIdAndOrigin(userId: number, origin: string): Promise<PasskeySelect[]> {
  return db.select().from(passkeys).where(and(eq(passkeys.userId, userId), eq(passkeys.origin, origin)));
}

export async function getPasskeyByCredentialId(userId: number, credentialId: string): Promise<PasskeySelect | undefined> {
  const [passkey] = await db
    .select()
    .from(passkeys)
    .where(and(eq(passkeys.userId, userId), eq(passkeys.credentialId, credentialId)));
  return passkey;
}

export async function createPasskey(data: PasskeyInsert): Promise<PasskeySelect> {
  const [passkey] = await db.insert(passkeys).values(data).returning();
  return passkey!;
}

export async function updatePasskey(id: number, data: Partial<Omit<PasskeySelect, 'id'>>): Promise<PasskeySelect | undefined> {
  const [passkey] = await db.update(passkeys).set(data).where(eq(passkeys.id, id)).returning();
  return passkey;
}

// ── Passkey Challenges ──

export async function storeChallenge(userId: number, origin: string, challenge: string, ttlMs: number) {
  const expiresAt = new Date(Date.now() + ttlMs);

  // Upsert: delete existing challenge for this user+origin, then insert
  await db.delete(passkeyChallenges).where(and(eq(passkeyChallenges.userId, userId), eq(passkeyChallenges.origin, origin)));
  await db.insert(passkeyChallenges).values({ userId, origin, challenge, expiresAt });
}

export async function consumeChallenge(userId: number, origin: string): Promise<string | null> {
  const now = new Date();

  // Clean up expired challenges
  await db.delete(passkeyChallenges).where(lt(passkeyChallenges.expiresAt, now));

  // Find and delete the matching challenge
  const [entry] = await db
    .delete(passkeyChallenges)
    .where(and(eq(passkeyChallenges.userId, userId), eq(passkeyChallenges.origin, origin)))
    .returning();

  if (!entry) return null;
  if (entry.expiresAt < now) return null;
  return entry.challenge;
}

// ── Token Blacklist ──

export async function blacklistToken(jti: string, expiresAt: number) {
  // expiresAt comes as Unix seconds from JWT exp claim
  const expiresDate = new Date(expiresAt * 1000);
  await db.insert(tokenBlacklist).values({ jti, expiresAt: expiresDate }).onConflictDoNothing();
}

export async function isTokenBlacklisted(jti: string): Promise<boolean> {
  const [entry] = await db.select({ jti: tokenBlacklist.jti }).from(tokenBlacklist).where(eq(tokenBlacklist.jti, jti));
  return !!entry;
}

export async function cleanupExpiredTokens() {
  await db.delete(tokenBlacklist).where(lt(tokenBlacklist.expiresAt, new Date()));
}
