import { eq } from 'drizzle-orm';
import { db } from '../db';
import { vaultTokens, vaultUnlockKeys } from './schema';
import { encryptSecret, decryptSecret } from '../crypto';

// Vault store access. Callers deal in PLAINTEXT — encryption to/from at-rest ciphertext happens here, so
// the token broker / proxy never touch the crypto directly. See ../crypto.ts and ../schema/vault.ts.

export type VaultTokenSet = {
  accessToken: string;
  refreshToken: string;
  expiresAt: Date | null;
  deviceIdentifier: string | null;
  clientId: string | null;
};

/** The owner's brokered Vaultwarden token set (decrypted), or null if none is stored. */
export async function getVaultTokens(userId: number): Promise<VaultTokenSet | null> {
  const [row] = await db.select().from(vaultTokens).where(eq(vaultTokens.userId, userId));
  if (!row) return null;
  return {
    accessToken: decryptSecret('vault', row.accessToken),
    refreshToken: decryptSecret('vault', row.refreshToken),
    expiresAt: row.expiresAt,
    deviceIdentifier: row.deviceIdentifier,
    clientId: row.clientId,
  };
}

/** Upsert the owner's token set (one row per account). Secrets are encrypted before write. */
export async function setVaultTokens(userId: number, t: VaultTokenSet): Promise<void> {
  const values = {
    userId,
    accessToken: encryptSecret('vault', t.accessToken),
    refreshToken: encryptSecret('vault', t.refreshToken),
    expiresAt: t.expiresAt,
    deviceIdentifier: t.deviceIdentifier,
    clientId: t.clientId,
    updatedAt: new Date(),
  };
  await db
    .insert(vaultTokens)
    .values(values)
    .onConflictDoUpdate({
      target: vaultTokens.userId,
      set: {
        accessToken: values.accessToken,
        refreshToken: values.refreshToken,
        expiresAt: values.expiresAt,
        deviceIdentifier: values.deviceIdentifier,
        clientId: values.clientId,
        updatedAt: values.updatedAt,
      },
    });
}

/** Update only the token pair + expiry after a refresh (keeps device/client). */
export async function updateVaultAccess(
  userId: number,
  accessToken: string,
  refreshToken: string,
  expiresAt: Date | null,
): Promise<void> {
  await db
    .update(vaultTokens)
    .set({
      accessToken: encryptSecret('vault', accessToken),
      refreshToken: encryptSecret('vault', refreshToken),
      expiresAt,
      updatedAt: new Date(),
    })
    .where(eq(vaultTokens.userId, userId));
}

/** Drop the token set (platform logout / distress / panic). */
export async function clearVaultTokens(userId: number): Promise<void> {
  await db.delete(vaultTokens).where(eq(vaultTokens.userId, userId));
}

/** The owner's stored protector key (decrypted), or null. Officer-app unlock path only. */
export async function getVaultUnlockKey(userId: number): Promise<string | null> {
  const [row] = await db.select().from(vaultUnlockKeys).where(eq(vaultUnlockKeys.userId, userId));
  return row ? decryptSecret('vault', row.wrappedKey) : null;
}

/** Store/replace the protector key (encrypted). Set once at setup; persists across normal logout. */
export async function setVaultUnlockKey(userId: number, wrappedKey: string): Promise<void> {
  const values = { userId, wrappedKey: encryptSecret('vault', wrappedKey), updatedAt: new Date() };
  await db
    .insert(vaultUnlockKeys)
    .values(values)
    .onConflictDoUpdate({
      target: vaultUnlockKeys.userId,
      set: { wrappedKey: values.wrappedKey, updatedAt: values.updatedAt },
    });
}

/** Wipe the protector key (distress / panic → forces a one-time master-password re-setup). */
export async function clearVaultUnlockKey(userId: number): Promise<void> {
  await db.delete(vaultUnlockKeys).where(eq(vaultUnlockKeys.userId, userId));
}
