// Crypto utilities for Cloudflare Workers

/**
 * Secure base64url encoding for Cloudflare Workers
 * Prevents stack overflow and encoding corruption with large buffers
 */
export function base64url(buffer: Uint8Array): string {
    // Handle empty buffer
    if (buffer.length === 0) {
        return '';
    }

    // For large buffers, process in chunks to prevent stack overflow
    const CHUNK_SIZE = 8192; // 8KB chunks - safe for all JS engines
    let result = '';

    for (let i = 0; i < buffer.length; i += CHUNK_SIZE) {
        const chunk = buffer.slice(i, i + CHUNK_SIZE);
        const chars = Array.from(chunk, byte => String.fromCharCode(byte));
        result += btoa(chars.join(''));
    }

    // Convert to base64url format (URL-safe)
    return result
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}

/**
 * Decode a base64url-encoded string back into bytes.
 * Accepts inputs with or without padding.
 */
export function base64urlDecode(input: string): Uint8Array {
    const padded = input.replace(/-/g, '+').replace(/_/g, '/') + '==='.slice((input.length + 3) % 4);
    return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
}

export function generatePortToken(): string {
    const array = new Uint8Array(12); // 12 bytes = 16 base64url chars
    crypto.getRandomValues(array);
    // Lowercase since hostnames are case-insensitive (RFC 1035)
    return base64url(array).toLowerCase().replace(/-/g, 'x');
}

export async function sha256Hash(text: string): Promise<string> {
    const encoder = new TextEncoder();
    const data = encoder.encode(text);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = new Uint8Array(hashBuffer);
    return Array.from(hashArray)
        .map(b => b.toString(16).padStart(2, '0'))
        .join('');
}

export async function timingSafeEqual(a: string, b: string): Promise<boolean> {
    const encoder = new TextEncoder();
    const aBuffer = encoder.encode(a);
    const bBuffer = encoder.encode(b);
    
    if (aBuffer.length !== bBuffer.length) {
        return false;
    }
    
    return crypto.subtle.timingSafeEqual(aBuffer, bBuffer);
}

export function timingSafeEqualBytes(a: Uint8Array, b: Uint8Array): boolean {
    if (a.length !== b.length) {
        return false;
    }
    
    return crypto.subtle.timingSafeEqual(a, b);
}

export function generateSecureToken(length: number = 32): string {
    const array = new Uint8Array(length);
    crypto.getRandomValues(array);
    return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}

export async function generateApiKey(): Promise<{
    key: string;
    keyHash: string;
    keyPreview: string;
}> {
    const keyBytes = new Uint8Array(32);
    crypto.getRandomValues(keyBytes);
    const key = base64url(keyBytes);
    const keyHash = await sha256Hash(key);
    const keyPreview = `${key.slice(0, 8)}...${key.slice(-4)}`;
    return { key, keyHash, keyPreview };
}

export async function verifyApiKey(providedKey: string, storedHash: string): Promise<boolean> {
    try {
        const providedKeyHash = await sha256Hash(providedKey);
        return await timingSafeEqual(providedKeyHash, storedHash);
    } catch {
        return false;
    }
}

export async function pbkdf2(
    password: string,
    salt: Uint8Array,
    iterations: number = 100000,
    keyLength: number = 32
): Promise<Uint8Array> {
    const encoder = new TextEncoder();
    
    const passwordKey = await crypto.subtle.importKey(
        'raw',
        encoder.encode(password),
        'PBKDF2',
        false,
        ['deriveBits']
    );
    
    const derivedBits = await crypto.subtle.deriveBits(
        {
            name: 'PBKDF2',
            salt,
            iterations,
            hash: 'SHA-256'
        },
        passwordKey,
        keyLength * 8
    );
    
    return new Uint8Array(derivedBits);
}
