/**
 * Cryptographic utilities for Synor wallet.
 *
 * ## Hybrid Quantum-Resistant Architecture
 *
 * Synor uses a hybrid signature scheme combining:
 * - **Ed25519** (classical): Fast, small signatures (64 bytes)
 * - **ML-DSA-65/Dilithium3** (quantum-resistant): Large signatures (~3.3KB)
 *
 * Both signatures must be valid for a transaction to be accepted. This provides
 * defense-in-depth: an attacker must break BOTH algorithms to forge a signature.
 *
 * ### Signing Modes
 *
 * 1. **Client-Side (WASM)**: Both Ed25519 and Dilithium3 signed in browser
 *    - Pro: Private keys never leave the device
 *    - Pro: Works offline
 *    - Con: ~2MB WASM module download
 *    - Use: `createHybridSignatureLocal()`
 *
 * 2. **Server-Side (RPC)**: Ed25519 client-side, Dilithium3 server-side
 *    - Pro: Faster initial load
 *    - Pro: Server can use secure enclaves
 *    - Con: Requires network, server holds Dilithium key
 *    - Use: `createHybridSignature()`
 *
 * ### Libraries Used
 * - BIP39 for mnemonic generation
 * - Ed25519 for classical signatures (via @noble/ed25519)
 * - Blake3 for hashing (via @noble/hashes)
 * - Dilithium3 via WASM (synor-crypto-wasm) or server RPC
 */

import * as bip39 from 'bip39';
import * as ed25519 from '@noble/ed25519';
import { blake3 } from '@noble/hashes/blake3';
import { sha512 } from '@noble/hashes/sha512';
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';

// Ed25519 requires sha512 for signing
ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));

export interface Keypair {
  publicKey: Uint8Array;
  privateKey: Uint8Array;
}

export interface WalletData {
  mnemonic: string;
  seed: Uint8Array;
  keypair: Keypair;
  address: string;
  /** Dilithium public key (1952 bytes) - only available if WASM loaded */
  dilithiumPublicKey?: Uint8Array;
}

/** Signing mode for hybrid signatures */
export type SigningMode = 'local' | 'server' | 'auto';

/** Configuration for hybrid signing */
export interface SigningConfig {
  /** Signing mode: 'local' (WASM), 'server' (RPC), or 'auto' */
  mode: SigningMode;
  /** RPC URL for server-side signing (required for 'server' and 'auto' modes) */
  rpcUrl?: string;
  /** Preload WASM module for faster first signature */
  preloadWasm?: boolean;
}

/**
 * Generate a new BIP39 mnemonic phrase.
 */
export function generateMnemonic(wordCount: 12 | 24 = 24): string {
  const strength = wordCount === 12 ? 128 : 256;
  return bip39.generateMnemonic(strength);
}

/**
 * Validate a BIP39 mnemonic phrase.
 */
export function validateMnemonic(mnemonic: string): boolean {
  return bip39.validateMnemonic(mnemonic);
}

/**
 * Derive seed from mnemonic with optional passphrase.
 */
export async function mnemonicToSeed(
  mnemonic: string,
  passphrase: string = ''
): Promise<Uint8Array> {
  const seed = await bip39.mnemonicToSeed(mnemonic, passphrase);
  return new Uint8Array(seed);
}

/**
 * Derive Ed25519 keypair from seed.
 * Uses the first 32 bytes of the seed as the private key.
 */
export async function deriveKeypair(seed: Uint8Array): Promise<Keypair> {
  // Use first 32 bytes of seed for Ed25519 private key
  const privateKey = seed.slice(0, 32);
  const publicKey = await ed25519.getPublicKeyAsync(privateKey);

  return { publicKey, privateKey };
}

/**
 * Generate Synor address from public key.
 *
 * Format: synor:<network_prefix><blake3_hash_first_20_bytes>
 * - qz = testnet
 * - q0 = mainnet (when launched)
 */
export function publicKeyToAddress(
  publicKey: Uint8Array,
  network: 'mainnet' | 'testnet' | 'devnet' = 'testnet'
): string {
  const hash = blake3(publicKey);
  const addressBytes = hash.slice(0, 20);

  // Network prefix
  const prefix = network === 'mainnet' ? 'q0' : 'qz';

  return `synor:${prefix}${bytesToHex(addressBytes)}`;
}

/**
 * Create a complete wallet from mnemonic.
 */
export async function createWallet(
  mnemonic: string,
  passphrase: string = '',
  network: 'mainnet' | 'testnet' | 'devnet' = 'testnet'
): Promise<WalletData> {
  const seed = await mnemonicToSeed(mnemonic, passphrase);
  const keypair = await deriveKeypair(seed);
  const address = publicKeyToAddress(keypair.publicKey, network);

  return {
    mnemonic,
    seed,
    keypair,
    address,
  };
}

/**
 * Sign a message with Ed25519.
 */
export async function sign(
  message: Uint8Array,
  privateKey: Uint8Array
): Promise<Uint8Array> {
  return await ed25519.signAsync(message, privateKey);
}

/**
 * Verify an Ed25519 signature.
 */
export async function verify(
  message: Uint8Array,
  signature: Uint8Array,
  publicKey: Uint8Array
): Promise<boolean> {
  return await ed25519.verifyAsync(signature, message, publicKey);
}

/**
 * Hash data with Blake3.
 */
export function hash(data: Uint8Array): Uint8Array {
  return blake3(data);
}

/**
 * Encrypt data with AES-GCM using a password-derived key.
 */
export async function encrypt(
  data: Uint8Array,
  password: string
): Promise<{ ciphertext: Uint8Array; iv: Uint8Array; salt: Uint8Array }> {
  const salt = crypto.getRandomValues(new Uint8Array(16));
  const iv = crypto.getRandomValues(new Uint8Array(12));

  const key = await deriveEncryptionKey(password, salt);
  const ciphertext = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },
    key,
    data.buffer as ArrayBuffer
  );

  return {
    ciphertext: new Uint8Array(ciphertext),
    iv,
    salt,
  };
}

/**
 * Decrypt AES-GCM encrypted data.
 */
export async function decrypt(
  ciphertext: Uint8Array,
  iv: Uint8Array,
  salt: Uint8Array,
  password: string
): Promise<Uint8Array> {
  const key = await deriveEncryptionKey(password, salt);
  const plaintext = await crypto.subtle.decrypt(
    { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },
    key,
    ciphertext.buffer as ArrayBuffer
  );

  return new Uint8Array(plaintext);
}

/**
 * Derive AES key from password using PBKDF2.
 */
async function deriveEncryptionKey(
  password: string,
  salt: Uint8Array
): Promise<CryptoKey> {
  const encoder = new TextEncoder();
  const passwordBytes = encoder.encode(password);
  const keyMaterial = await crypto.subtle.importKey(
    'raw',
    passwordBytes.buffer as ArrayBuffer,
    'PBKDF2',
    false,
    ['deriveKey']
  );

  return await crypto.subtle.deriveKey(
    {
      name: 'PBKDF2',
      salt: salt.buffer as ArrayBuffer,
      iterations: 100000,
      hash: 'SHA-256',
    },
    keyMaterial,
    { name: 'AES-GCM', length: 256 },
    false,
    ['encrypt', 'decrypt']
  );
}

// ==================== Hybrid Signature Support ====================

/**
 * A hybrid signature containing both Ed25519 and ML-DSA-65 components.
 * Both must verify for the signature to be valid.
 */
export interface HybridSignature {
  /** Ed25519 signature (64 bytes) */
  ed25519: Uint8Array;
  /** ML-DSA-65 signature (~3309 bytes) */
  dilithium: Uint8Array;
}

/**
 * Serialize a hybrid signature to bytes.
 * Format: ed25519_signature (64 bytes) || dilithium_signature (variable)
 */
export function serializeHybridSignature(sig: HybridSignature): Uint8Array {
  const result = new Uint8Array(sig.ed25519.length + sig.dilithium.length);
  result.set(sig.ed25519, 0);
  result.set(sig.dilithium, sig.ed25519.length);
  return result;
}

/**
 * Deserialize a hybrid signature from bytes.
 */
export function deserializeHybridSignature(bytes: Uint8Array): HybridSignature {
  if (bytes.length < 64) {
    throw new Error('Invalid hybrid signature: too short');
  }
  return {
    ed25519: bytes.slice(0, 64),
    dilithium: bytes.slice(64),
  };
}

/**
 * Create a hybrid signature by signing with Ed25519 locally
 * and requesting Dilithium signature from the server.
 *
 * @param message - The message to sign
 * @param privateKey - Ed25519 private key for local signing
 * @param rpcUrl - URL of the Synor node RPC endpoint
 * @returns Promise resolving to the hybrid signature
 */
export async function createHybridSignature(
  message: Uint8Array,
  privateKey: Uint8Array,
  rpcUrl: string
): Promise<HybridSignature> {
  // Sign locally with Ed25519
  const ed25519Signature = await sign(message, privateKey);

  // Request Dilithium signature from server
  // The server holds the Dilithium private key corresponding to this wallet
  const dilithiumSignature = await requestDilithiumSignature(
    message,
    ed25519Signature,
    rpcUrl
  );

  return {
    ed25519: ed25519Signature,
    dilithium: dilithiumSignature,
  };
}

/**
 * Request a Dilithium signature from the server.
 *
 * The server verifies the Ed25519 signature first to ensure the request
 * is from the legitimate key holder, then signs with the corresponding
 * Dilithium key.
 */
async function requestDilithiumSignature(
  message: Uint8Array,
  ed25519Signature: Uint8Array,
  rpcUrl: string
): Promise<Uint8Array> {
  const response = await fetch(rpcUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'wallet_signDilithium',
      params: {
        message: bytesToHex(message),
        ed25519Signature: bytesToHex(ed25519Signature),
      },
      id: 1,
    }),
  });

  const result = await response.json();
  if (result.error) {
    throw new Error(`Dilithium signing failed: ${result.error.message}`);
  }

  return hexToBytes(result.result.signature);
}

/**
 * Verify a hybrid signature (Ed25519 component only - client-side).
 *
 * Full hybrid verification including Dilithium is done by the node
 * when submitting transactions. This function is useful for quick
 * client-side validation of the Ed25519 component.
 */
export async function verifyHybridSignatureEd25519(
  message: Uint8Array,
  signature: HybridSignature,
  publicKey: Uint8Array
): Promise<boolean> {
  return await verify(message, signature.ed25519, publicKey);
}

// ==================== Client-Side Dilithium (WASM) ====================

import {
  loadWasmCrypto,
  isWasmLoaded,
  signWithDilithium,
  createDilithiumKeyFromSeed,
  unloadWasmCrypto,
} from './wasm-crypto';

/**
 * Preload the WASM crypto module for faster first signature.
 *
 * Call this early (e.g., on app load or when user navigates to send page)
 * to reduce latency for the first local signature.
 *
 * @returns Promise that resolves when WASM is loaded
 */
export async function preloadWasmCrypto(): Promise<void> {
  await loadWasmCrypto();
}

/**
 * Check if the WASM crypto module is loaded.
 */
export function isWasmCryptoLoaded(): boolean {
  return isWasmLoaded();
}

/**
 * Unload the WASM module to free memory (~2MB).
 *
 * Call this when the user is done signing transactions
 * to reclaim browser memory.
 */
export function freeWasmCrypto(): void {
  unloadWasmCrypto();
}

/**
 * Create a hybrid signature entirely client-side using WASM.
 *
 * This function signs with both Ed25519 and Dilithium3 locally,
 * without any server communication. The private keys never leave
 * the browser.
 *
 * @param message - The message to sign
 * @param seed - The 64-byte seed from BIP-39 mnemonic
 * @returns Promise resolving to the hybrid signature
 */
export async function createHybridSignatureLocal(
  message: Uint8Array,
  seed: Uint8Array
): Promise<HybridSignature> {
  // Sign with Ed25519 (first 32 bytes of seed)
  const ed25519PrivateKey = seed.slice(0, 32);
  const ed25519Signature = await sign(message, ed25519PrivateKey);

  // Sign with Dilithium3 using WASM (use full seed for key derivation)
  const dilithiumSignature = await signWithDilithium(message, seed);

  return {
    ed25519: ed25519Signature,
    dilithium: dilithiumSignature,
  };
}

/**
 * Get the Dilithium3 public key for a wallet.
 *
 * This is needed when registering a new wallet on the blockchain,
 * as the hybrid public key includes both Ed25519 and Dilithium components.
 *
 * @param seed - The 64-byte seed from BIP-39 mnemonic
 * @returns Promise resolving to the Dilithium public key (1952 bytes)
 */
export async function getDilithiumPublicKey(seed: Uint8Array): Promise<Uint8Array> {
  const dilithiumKey = await createDilithiumKeyFromSeed(seed);
  try {
    return dilithiumKey.publicKey();
  } finally {
    dilithiumKey.free();
  }
}

/**
 * Create a wallet with both Ed25519 and Dilithium3 keys.
 *
 * This is an enhanced version of `createWallet()` that also derives
 * the Dilithium public key for quantum-resistant transactions.
 *
 * @param mnemonic - BIP-39 mnemonic phrase
 * @param passphrase - Optional passphrase
 * @param network - Network type
 * @returns Promise resolving to wallet data with Dilithium public key
 */
export async function createWalletWithDilithium(
  mnemonic: string,
  passphrase: string = '',
  network: 'mainnet' | 'testnet' | 'devnet' = 'testnet'
): Promise<WalletData> {
  const seed = await mnemonicToSeed(mnemonic, passphrase);
  const keypair = await deriveKeypair(seed);
  const address = publicKeyToAddress(keypair.publicKey, network);

  // Derive Dilithium public key using WASM
  const dilithiumPublicKey = await getDilithiumPublicKey(seed);

  return {
    mnemonic,
    seed,
    keypair,
    address,
    dilithiumPublicKey,
  };
}

/**
 * Verify a hybrid signature client-side using WASM.
 *
 * Verifies both the Ed25519 and Dilithium components of a hybrid signature.
 *
 * @param message - The original message
 * @param signature - The hybrid signature to verify
 * @param ed25519PublicKey - Ed25519 public key (32 bytes)
 * @param dilithiumPublicKey - Dilithium public key (1952 bytes)
 * @returns Promise resolving to true if both signatures are valid
 */
export async function verifyHybridSignatureLocal(
  message: Uint8Array,
  signature: HybridSignature,
  ed25519PublicKey: Uint8Array,
  dilithiumPublicKey: Uint8Array
): Promise<boolean> {
  // Import Dilithium verification from WASM
  const { verifyDilithiumSignature } = await import('./wasm-crypto');

  // Verify Ed25519 signature
  const ed25519Valid = await verify(message, signature.ed25519, ed25519PublicKey);
  if (!ed25519Valid) {
    return false;
  }

  // Verify Dilithium signature
  const dilithiumValid = await verifyDilithiumSignature(
    message,
    signature.dilithium,
    dilithiumPublicKey
  );

  return dilithiumValid;
}

/**
 * Smart hybrid signature creation that auto-selects the best method.
 *
 * - Uses local WASM if already loaded (fastest)
 * - Falls back to server RPC if WASM not loaded and RPC URL provided
 * - Loads WASM if no RPC URL (fully client-side)
 *
 * @param message - The message to sign
 * @param seed - The 64-byte seed (for local signing) or 32-byte private key (for server)
 * @param config - Signing configuration
 * @returns Promise resolving to the hybrid signature
 */
export async function createHybridSignatureSmart(
  message: Uint8Array,
  seed: Uint8Array,
  config: SigningConfig = { mode: 'auto' }
): Promise<HybridSignature> {
  const { mode, rpcUrl } = config;

  // Explicit mode selection
  if (mode === 'local') {
    return createHybridSignatureLocal(message, seed);
  }

  if (mode === 'server') {
    if (!rpcUrl) {
      throw new Error('RPC URL required for server-side signing');
    }
    const privateKey = seed.slice(0, 32);
    return createHybridSignature(message, privateKey, rpcUrl);
  }

  // Auto mode: prefer local if WASM loaded, otherwise use server
  if (isWasmLoaded()) {
    return createHybridSignatureLocal(message, seed);
  }

  if (rpcUrl) {
    // Server available, use it to avoid WASM load time
    const privateKey = seed.slice(0, 32);
    return createHybridSignature(message, privateKey, rpcUrl);
  }

  // No server, must use local
  return createHybridSignatureLocal(message, seed);
}

// Re-export utilities
export { bytesToHex, hexToBytes };

// Re-export WASM utilities for advanced usage
export { loadWasmCrypto, isWasmLoaded, unloadWasmCrypto } from './wasm-crypto';
