const CHARSET = {
  uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
  lowercase: 'abcdefghijklmnopqrstuvwxyz',
  numbers: '0123456789',
  symbols: '!@#$%^&*()-_=+[]{}|;:,.<>?',
  hex: '0123456789abcdef',
  base62: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
};

export interface PasswordOpts {
  length: number;
  uppercase: boolean;
  lowercase: boolean;
  numbers: boolean;
  symbols: boolean;
}

export interface ApiKeyOpts {
  length: number;
  format: 'hex' | 'base62' | 'base64url';
  prefix: string;
}

export interface HashOpts {
  algorithm: 'SHA-1' | 'SHA-256' | 'SHA-512';
  input: string;
}

export interface TokenOpts {
  bytes: number;
  format: 'hex' | 'base64url';
}

function randomBytes(n: number): Uint8Array {
  const arr = new Uint8Array(n);
  crypto.getRandomValues(arr);
  return arr;
}

function toHex(bytes: Uint8Array): string {
  return Array.from(bytes)
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('');
}

function toBase64url(bytes: Uint8Array): string {
  return btoa(String.fromCharCode(...bytes))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
}

export function generatePassword(opts: PasswordOpts): string {
  let charset = '';
  if (opts.uppercase) charset += CHARSET.uppercase;
  if (opts.lowercase) charset += CHARSET.lowercase;
  if (opts.numbers) charset += CHARSET.numbers;
  if (opts.symbols) charset += CHARSET.symbols;
  if (!charset) charset = CHARSET.lowercase + CHARSET.numbers;

  const bytes = randomBytes(opts.length * 4);
  let result = '';
  let i = 0;
  while (result.length < opts.length && i < bytes.length) {
    const idx = bytes[i] % charset.length;
    result += charset[idx];
    i++;
  }
  return result.slice(0, opts.length);
}

export function passwordEntropy(opts: PasswordOpts): number {
  let size = 0;
  if (opts.uppercase) size += 26;
  if (opts.lowercase) size += 26;
  if (opts.numbers) size += 10;
  if (opts.symbols) size += CHARSET.symbols.length;
  if (size === 0) size = 36;
  return Math.round(Math.log2(size) * opts.length);
}

export function generateUUID(): string {
  return crypto.randomUUID();
}

export function generateApiKey(opts: ApiKeyOpts): string {
  const bytes = randomBytes(opts.length * 2);
  let key: string;
  switch (opts.format) {
    case 'hex':
      key = toHex(bytes).slice(0, opts.length);
      break;
    case 'base64url':
      key = toBase64url(bytes).slice(0, opts.length);
      break;
    case 'base62': {
      const cs = CHARSET.base62;
      key = Array.from(bytes)
        .map((b) => cs[b % cs.length])
        .join('')
        .slice(0, opts.length);
      break;
    }
  }
  return opts.prefix ? `${opts.prefix}_${key}` : key;
}

export async function generateHash(opts: HashOpts): Promise<string> {
  const data = opts.input.trim() || toHex(randomBytes(32));
  const encoded = new TextEncoder().encode(data);
  const hashBuffer = await crypto.subtle.digest(opts.algorithm, encoded);
  return toHex(new Uint8Array(hashBuffer));
}

export function generateToken(opts: TokenOpts): string {
  const bytes = randomBytes(opts.bytes);
  return opts.format === 'hex' ? toHex(bytes) : toBase64url(bytes);
}
