/**
 * @wrnexus/encryption — authenticated symmetric encryption (AES-256-GCM) via
 * WebCrypto, dependency-free. Use it to encrypt secrets, tokens, or database
 * fields at rest.
 *
 *   const key = await generateKey();                 // store this safely
 *   const box = await encrypt("card #1234", key);    // opaque base64 string
 *   const plain = await decrypt(box, key);           // "card #1234"
 *
 * A key derived from a password (PBKDF2) is also supported via `deriveKey`.
 */

const enc = new TextEncoder();
const dec = new TextDecoder();
const IV_BYTES = 12;

function toB64(bytes: Uint8Array): string {
  let bin = "";
  for (const b of bytes) bin += String.fromCharCode(b);
  return btoa(bin);
}
function fromB64(str: string): Uint8Array {
  const bin = atob(str);
  const out = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
  return out;
}

function toHex(bytes: Uint8Array): string {
  let out = "";
  for (const b of bytes) out += b.toString(16).padStart(2, "0");
  return out;
}

/** SHA-256 hex digest of a string (e.g. content hashing, dedup keys). */
export async function sha256(data: string): Promise<string> {
  const digest = await crypto.subtle.digest("SHA-256", enc.encode(data) as BufferSource);
  return toHex(new Uint8Array(digest));
}

/** HMAC-SHA256 hex signature of `data` with `secret` (e.g. signing webhooks). */
export async function hmacSign(data: string, secret: string): Promise<string> {
  const key = await crypto.subtle.importKey(
    "raw",
    enc.encode(secret) as BufferSource,
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"],
  );
  const sig = await crypto.subtle.sign("HMAC", key, enc.encode(data) as BufferSource);
  return toHex(new Uint8Array(sig));
}

/** Constant-time verify of an HMAC-SHA256 signature. */
export async function hmacVerify(
  data: string,
  secret: string,
  signature: string,
): Promise<boolean> {
  const expected = await hmacSign(data, secret);
  if (expected.length !== signature.length) return false;
  let diff = 0;
  for (let i = 0; i < expected.length; i++)
    diff |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
  return diff === 0;
}

/** Generate a random 256-bit key, base64-encoded. Store it as a secret. */
export async function generateKey(): Promise<string> {
  const bytes = new Uint8Array(32);
  crypto.getRandomValues(bytes);
  return toB64(bytes);
}

async function importAesKey(key: string): Promise<CryptoKey> {
  const raw = fromB64(key);
  if (raw.length !== 32) throw new Error("Encryption key must be a base64 256-bit key");
  return crypto.subtle.importKey("raw", raw as BufferSource, "AES-GCM", false, [
    "encrypt",
    "decrypt",
  ]);
}

/**
 * Encrypt a string. Output is base64 of `iv(12) || ciphertext+tag`, safe to
 * store or transmit. Each call uses a fresh random IV.
 */
export async function encrypt(plaintext: string, key: string): Promise<string> {
  const cryptoKey = await importAesKey(key);
  const iv = new Uint8Array(IV_BYTES);
  crypto.getRandomValues(iv);
  const ciphertext = new Uint8Array(
    await crypto.subtle.encrypt(
      { name: "AES-GCM", iv: iv as BufferSource },
      cryptoKey,
      enc.encode(plaintext) as BufferSource,
    ),
  );
  const packed = new Uint8Array(iv.length + ciphertext.length);
  packed.set(iv, 0);
  packed.set(ciphertext, iv.length);
  return toB64(packed);
}

/** Decrypt a value produced by `encrypt`. Throws if the key is wrong or data tampered. */
export async function decrypt(payload: string, key: string): Promise<string> {
  const cryptoKey = await importAesKey(key);
  const packed = fromB64(payload);
  if (packed.length <= IV_BYTES) throw new Error("Invalid ciphertext");
  const iv = packed.slice(0, IV_BYTES);
  const ciphertext = packed.slice(IV_BYTES);
  const plain = await crypto.subtle.decrypt(
    { name: "AES-GCM", iv: iv as BufferSource },
    cryptoKey,
    ciphertext as BufferSource,
  );
  return dec.decode(plain);
}

/** Derive a base64 AES key from a password + salt (PBKDF2, 100k iterations). */
export async function deriveKey(password: string, salt: string): Promise<string> {
  const baseKey = await crypto.subtle.importKey(
    "raw",
    enc.encode(password) as BufferSource,
    "PBKDF2",
    false,
    ["deriveBits"],
  );
  const bits = await crypto.subtle.deriveBits(
    {
      name: "PBKDF2",
      salt: enc.encode(salt) as BufferSource,
      iterations: 100_000,
      hash: "SHA-256",
    },
    baseKey,
    256,
  );
  return toB64(new Uint8Array(bits));
}
export { createKeyring, seal, open, sealedKeyId, needsRotation } from "./keyring.ts";
export type { EncryptionKey, EncryptionKeyring } from "./keyring.ts";
export {
  ENCRYPTED_HTTP_CONTENT_TYPE,
  ENCRYPTED_HTTP_VERSION,
  createMemoryReplayStore,
  encryptHttpBody,
  decryptHttpBody,
  createEncryptedRequest,
  decryptRequest,
  encryptResponse,
  decryptEncryptedResponse,
  encryptedFetch,
  encryptedBody,
  encryptedExchange,
} from "./http.ts";
export type {
  EncryptedHttpEnvelope,
  EncryptedHttpOptions,
  DecryptedHttpBody,
  ReplayStore,
} from "./http.ts";
