import { compare } from 'bcryptjs';

const BCRYPT_HASH_PATTERN = /^\$2[aby]\$(0[4-9]|[12]\d|3[01])\$[./A-Za-z0-9]{53}$/;
const DUMMY_BCRYPT_HASH =
  '$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy';

export const MAX_PASSWORD_BYTES = 72;

export function isUsablePassword(value: unknown): value is string {
  return (
    typeof value === 'string' &&
    value.length > 0 &&
    Buffer.byteLength(value, 'utf8') <= MAX_PASSWORD_BYTES
  );
}

function normalizeSupabaseBcryptHash(hash: string): string | null {
  if (!BCRYPT_HASH_PATTERN.test(hash)) {
    return null;
  }

  // bcryptjs accepts 2a/2b. Some imported bcrypt implementations label the
  // same format as 2y, so normalize only that version marker.
  return hash.startsWith('$2y$') ? `$2b$${hash.slice(4)}` : hash;
}

/**
 * Verifies a password against a bcrypt hash imported verbatim from Supabase.
 * A missing or malformed hash still performs a bcrypt comparison so callers
 * do not expose whether an email exists through the fast failure path.
 */
export async function verifyPassword(
  password: unknown,
  encryptedPassword: unknown,
): Promise<boolean> {
  const usablePassword = isUsablePassword(password);
  const normalizedHash =
    typeof encryptedPassword === 'string'
      ? normalizeSupabaseBcryptHash(encryptedPassword)
      : null;

  try {
    const matches = await compare(
      usablePassword ? password : '',
      normalizedHash ?? DUMMY_BCRYPT_HASH,
    );

    return usablePassword && normalizedHash !== null && matches;
  } catch {
    return false;
  }
}
