import type { CryptoProvider } from '@shade/core';
import { x25519 } from '@noble/curves/ed25519.js';
import { ed25519 } from '@noble/curves/ed25519.js';

/**
 * Cast a Uint8Array to a SubtleCrypto-compatible buffer source. TS 5.7+
 * tightened Uint8Array's buffer generic to ArrayBuffer, but our public API
 * accepts Uint8Array<ArrayBufferLike>. SubtleCrypto methods are runtime-
 * compatible with both — only the type system needs the bridge.
 *
 * Returns `ArrayBuffer` (rather than the DOM `BufferSource` alias) so this
 * file type-checks without DOM lib in the consumer's tsconfig.
 */
function bs(u: Uint8Array | undefined): ArrayBuffer {
  return u as unknown as ArrayBuffer;
}

/**
 * SubtleCrypto + noble/curves implementation of CryptoProvider.
 *
 * Uses @noble/curves for X25519 and Ed25519 (reliable across all runtimes)
 * and Web Crypto API for AES-256-GCM, HKDF, and HMAC (hardware-accelerated).
 */
export class SubtleCryptoProvider implements CryptoProvider {
  private readonly subtle: SubtleCrypto;

  constructor(subtle?: SubtleCrypto) {
    this.subtle = subtle ?? globalThis.crypto.subtle;
  }

  // ─── X25519 (via @noble/curves) ────────────────────────────

  async generateX25519KeyPair(): Promise<{ publicKey: Uint8Array; privateKey: Uint8Array }> {
    const privateKey = this.randomBytes(32);
    const publicKey = x25519.getPublicKey(privateKey);
    return { publicKey, privateKey };
  }

  async x25519(privateKey: Uint8Array, publicKey: Uint8Array): Promise<Uint8Array> {
    return x25519.getSharedSecret(privateKey, publicKey);
  }

  // ─── Ed25519 (via @noble/curves) ───────────────────────────

  async generateEd25519KeyPair(): Promise<{ publicKey: Uint8Array; privateKey: Uint8Array }> {
    const privateKey = this.randomBytes(32);
    const publicKey = ed25519.getPublicKey(privateKey);
    return { publicKey, privateKey };
  }

  async sign(privateKey: Uint8Array, message: Uint8Array): Promise<Uint8Array> {
    return ed25519.sign(message, privateKey);
  }

  async verify(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): Promise<boolean> {
    try {
      return ed25519.verify(signature, message, publicKey);
    } catch {
      return false;
    }
  }

  // ─── AES-256-GCM (via SubtleCrypto) ───────────────────────

  async aesGcmEncrypt(
    key: Uint8Array,
    plaintext: Uint8Array,
    aad?: Uint8Array,
  ): Promise<{ ciphertext: Uint8Array; nonce: Uint8Array }> {
    const nonce = this.randomBytes(12);
    const aesKey = await this.subtle.importKey('raw', bs(key), 'AES-GCM', false, ['encrypt']);
    const encrypted = await this.subtle.encrypt(
      { name: 'AES-GCM', iv: bs(nonce), additionalData: aad ? bs(aad) : undefined },
      aesKey,
      bs(plaintext),
    );
    return { ciphertext: new Uint8Array(encrypted), nonce };
  }

  async aesGcmDecrypt(
    key: Uint8Array,
    ciphertext: Uint8Array,
    nonce: Uint8Array,
    aad?: Uint8Array,
  ): Promise<Uint8Array> {
    const aesKey = await this.subtle.importKey('raw', bs(key), 'AES-GCM', false, ['decrypt']);
    const decrypted = await this.subtle.decrypt(
      { name: 'AES-GCM', iv: bs(nonce), additionalData: aad ? bs(aad) : undefined },
      aesKey,
      bs(ciphertext),
    );
    return new Uint8Array(decrypted);
  }

  // ─── Key Derivation (via SubtleCrypto) ─────────────────────

  async hkdf(
    ikm: Uint8Array,
    salt: Uint8Array,
    info: Uint8Array,
    length: number,
  ): Promise<Uint8Array> {
    const baseKey = await this.subtle.importKey('raw', bs(ikm), 'HKDF', false, ['deriveBits']);
    const bits = await this.subtle.deriveBits(
      { name: 'HKDF', hash: 'SHA-256', salt: bs(salt), info: bs(info) },
      baseKey,
      length * 8,
    );
    return new Uint8Array(bits);
  }

  async hmacSha256(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {
    const hmacKey = await this.subtle.importKey(
      'raw',
      bs(key),
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign'],
    );
    const sig = await this.subtle.sign('HMAC', hmacKey, bs(data));
    return new Uint8Array(sig);
  }

  // ─── Random ────────────────────────────────────────────────

  randomBytes(length: number): Uint8Array {
    const buf = new Uint8Array(length);
    globalThis.crypto.getRandomValues(buf);
    return buf;
  }

  // ─── Hardening ─────────────────────────────────────────────

  constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {
    if (a.length !== b.length) return false;
    let diff = 0;
    for (let i = 0; i < a.length; i++) {
      diff |= a[i]! ^ b[i]!;
    }
    return diff === 0;
  }

  zeroize(buf: Uint8Array): void {
    buf.fill(0);
  }

  randomUint32(): number {
    const buf = this.randomBytes(4);
    return new DataView(buf.buffer, buf.byteOffset, 4).getUint32(0, false);
  }
}
