import { hmacSign, sha256 } from "@wrnexus/encryption";

const encoder = new TextEncoder();
const MAX_RANDOM_ROUNDS = 128;

export function bytesToBase64Url(bytes: Uint8Array): string {
  let binary = "";
  for (const byte of bytes) binary += String.fromCharCode(byte);
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}

export function base64UrlToBytes(value: string): Uint8Array {
  if (!/^[A-Za-z0-9_-]*$/.test(value) || value.length % 4 === 1) {
    throw new TypeError("Invalid base64url value");
  }
  const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
  const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
  let binary: string;
  try {
    binary = atob(padded);
  } catch {
    throw new TypeError("Invalid base64url value");
  }
  const bytes = new Uint8Array(binary.length);
  for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
  if (bytesToBase64Url(bytes) !== value) throw new TypeError("Invalid base64url value");
  return bytes;
}

export function randomToken(random: (length: number) => Uint8Array, bytes = 32): string {
  if (!Number.isInteger(bytes) || bytes <= 0) {
    throw new RangeError("random token byte length must be a positive integer");
  }
  const value = random(bytes);
  if (!(value instanceof Uint8Array) || value.length !== bytes) {
    throw new TypeError(`random byte provider must return exactly ${bytes} bytes`);
  }
  return bytesToBase64Url(value);
}

export function randomDigits(random: (length: number) => Uint8Array, length = 6): string {
  return randomFromAlphabet(random, "0123456789", length);
}

export function randomReadableCode(random: (length: number) => Uint8Array, length = 10): string {
  return randomFromAlphabet(random, "ABCDEFGHJKLMNPQRSTUVWXYZ23456789", length);
}

function randomFromAlphabet(
  random: (length: number) => Uint8Array,
  alphabet: string,
  length: number,
): string {
  if (!Number.isInteger(length) || length < 0) throw new RangeError("length must be non-negative");
  if (!alphabet.length || alphabet.length > 256 || new Set(alphabet).size !== alphabet.length) {
    throw new TypeError("alphabet must contain 1 to 256 unique characters");
  }
  if (length === 0) return "";
  const limit = Math.floor(256 / alphabet.length) * alphabet.length;
  let output = "";
  for (let round = 0; output.length < length && round < MAX_RANDOM_ROUNDS; round += 1) {
    const requested = Math.max(16, (length - output.length) * 2);
    const bytes = random(requested);
    if (!(bytes instanceof Uint8Array) || bytes.length !== requested) {
      throw new TypeError(`random byte provider must return exactly ${requested} bytes`);
    }
    for (const byte of bytes) {
      if (byte >= limit) continue;
      output += alphabet[byte % alphabet.length];
      if (output.length === length) break;
    }
  }
  if (output.length !== length) {
    throw new Error("WRN-AUTH-RANDOM-SOURCE-REJECTED");
  }
  return output;
}

export async function hashSecret(value: string, secret: string): Promise<string> {
  return hmacSign(value, secret);
}

export async function fingerprint(value: string): Promise<string> {
  return sha256(value);
}

export async function constantTimeEqual(left: string, right: string): Promise<boolean> {
  const leftBytes = encoder.encode(left);
  const rightBytes = encoder.encode(right);
  const length = Math.max(leftBytes.length, rightBytes.length);
  let diff = leftBytes.length ^ rightBytes.length;
  for (let index = 0; index < length; index += 1) {
    diff |= (leftBytes[index] ?? 0) ^ (rightBytes[index] ?? 0);
  }
  return diff === 0;
}
