/**
 * End-to-End Encryption Manager
 * Provides E2E encryption using NaCl (TweetNaCl)
 */

import * as nacl from 'tweetnacl';
import * as util from 'tweetnacl-util';

export interface KeyPair {
  publicKey: string;
  secretKey: string;
}

export interface EncryptedMessage {
  ciphertext: string;
  nonce: string;
  ephemeralPublicKey: string;
}

export class CryptoManager {
  private keyPair: nacl.BoxKeyPair | null = null;
  
  /**
   * Generate a new key pair
   */
  generateKeyPair(): KeyPair {
    this.keyPair = nacl.box.keyPair();
    
    return {
      publicKey: util.encodeBase64(this.keyPair.publicKey),
      secretKey: util.encodeBase64(this.keyPair.secretKey),
    };
  }
  
  /**
   * Load existing key pair
   */
  loadKeyPair(keyPair: KeyPair): void {
    this.keyPair = {
      publicKey: util.decodeBase64(keyPair.publicKey),
      secretKey: util.decodeBase64(keyPair.secretKey),
    };
  }
  
  /**
   * Encrypt a message for a recipient
   */
  encrypt(message: string, recipientPublicKey: string): EncryptedMessage {
    if (!this.keyPair) {
      throw new Error('Key pair not initialized');
    }
    
    // Generate ephemeral key pair for this message
    const ephemeralKeyPair = nacl.box.keyPair();
    
    // Generate random nonce
    const nonce = nacl.randomBytes(nacl.box.nonceLength);
    
    // Convert message to Uint8Array
    const messageUint8 = util.decodeUTF8(message);
    
    // Decrypt recipient's public key
    const recipientPublicKeyUint8 = util.decodeBase64(recipientPublicKey);
    
    // Encrypt message
    const ciphertext = nacl.box(
      messageUint8,
      nonce,
      recipientPublicKeyUint8,
      ephemeralKeyPair.secretKey
    );
    
    return {
      ciphertext: util.encodeBase64(ciphertext),
      nonce: util.encodeBase64(nonce),
      ephemeralPublicKey: util.encodeBase64(ephemeralKeyPair.publicKey),
    };
  }
  
  /**
   * Decrypt a message
   */
  decrypt(encryptedMessage: EncryptedMessage): string {
    if (!this.keyPair) {
      throw new Error('Key pair not initialized');
    }
    
    // Decode components
    const ciphertext = util.decodeBase64(encryptedMessage.ciphertext);
    const nonce = util.decodeBase64(encryptedMessage.nonce);
    const ephemeralPublicKey = util.decodeBase64(encryptedMessage.ephemeralPublicKey);
    
    // Decrypt message
    const decrypted = nacl.box.open(
      ciphertext,
      nonce,
      ephemeralPublicKey,
      this.keyPair.secretKey
    );
    
    if (!decrypted) {
      throw new Error('Failed to decrypt message');
    }
    
    return util.encodeUTF8(decrypted);
  }
  
  /**
   * Generate a symmetric key for group encryption
   */
  generateSymmetricKey(): string {
    const key = nacl.randomBytes(nacl.secretbox.keyLength);
    return util.encodeBase64(key);
  }
  
  /**
   * Encrypt with symmetric key (for group messages)
   */
  encryptSymmetric(message: string, key: string): { ciphertext: string; nonce: string } {
    const keyUint8 = util.decodeBase64(key);
    const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);
    const messageUint8 = util.decodeUTF8(message);
    
    const ciphertext = nacl.secretbox(messageUint8, nonce, keyUint8);
    
    return {
      ciphertext: util.encodeBase64(ciphertext),
      nonce: util.encodeBase64(nonce),
    };
  }
  
  /**
   * Decrypt with symmetric key
   */
  decryptSymmetric(ciphertext: string, nonce: string, key: string): string {
    const keyUint8 = util.decodeBase64(key);
    const nonceUint8 = util.decodeBase64(nonce);
    const ciphertextUint8 = util.decodeBase64(ciphertext);
    
    const decrypted = nacl.secretbox.open(ciphertextUint8, nonceUint8, keyUint8);
    
    if (!decrypted) {
      throw new Error('Failed to decrypt message');
    }
    
    return util.encodeUTF8(decrypted);
  }
  
  /**
   * Hash a value (for password hashing, checksums, etc.)
   */
  hash(value: string): string {
    const valueUint8 = util.decodeUTF8(value);
    const hash = nacl.hash(valueUint8);
    return util.encodeBase64(hash);
  }
  
  /**
   * Generate a random string (for tokens, IDs, etc.)
   */
  generateRandomString(length: number = 32): string {
    const randomBytes = nacl.randomBytes(length);
    return util.encodeBase64(randomBytes);
  }
  
  /**
   * Sign a message
   */
  sign(message: string): string {
    if (!this.keyPair) {
      throw new Error('Key pair not initialized');
    }
    
    // Generate signing key pair from box key pair
    const signingKeyPair = nacl.sign.keyPair.fromSeed(this.keyPair.secretKey.slice(0, 32));
    
    const messageUint8 = util.decodeUTF8(message);
    const signature = nacl.sign.detached(messageUint8, signingKeyPair.secretKey);
    
    return util.encodeBase64(signature);
  }
  
  /**
   * Verify a signature
   */
  verify(message: string, signature: string, publicKey: string): boolean {
    const messageUint8 = util.decodeUTF8(message);
    const signatureUint8 = util.decodeBase64(signature);
    
    // Derive signing public key from box public key
    const boxPublicKey = util.decodeBase64(publicKey);
    // For simplicity, we'll use the first 32 bytes as signing key
    // In production, you'd want a separate signing key pair
    const signingPublicKey = boxPublicKey.slice(0, 32);
    
    return nacl.sign.detached.verify(messageUint8, signatureUint8, signingPublicKey);
  }
  
  /**
   * Store keys securely (localStorage with encryption)
   */
  async storeKeys(password: string): Promise<void> {
    if (!this.keyPair) {
      throw new Error('Key pair not initialized');
    }
    
    // Derive a key from password
    const passwordHash = this.hash(password);
    const derivedKey = util.decodeBase64(passwordHash).slice(0, nacl.secretbox.keyLength);
    
    // Encrypt secret key
    const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);
    const encryptedSecretKey = nacl.secretbox(this.keyPair.secretKey, nonce, derivedKey);
    
    // Store in localStorage
    localStorage.setItem('aethex_public_key', util.encodeBase64(this.keyPair.publicKey));
    localStorage.setItem('aethex_encrypted_secret_key', util.encodeBase64(encryptedSecretKey));
    localStorage.setItem('aethex_key_nonce', util.encodeBase64(nonce));
  }
  
  /**
   * Load keys from storage
   */
  async loadStoredKeys(password: string): Promise<KeyPair | null> {
    const publicKeyStr = localStorage.getItem('aethex_public_key');
    const encryptedSecretKeyStr = localStorage.getItem('aethex_encrypted_secret_key');
    const nonceStr = localStorage.getItem('aethex_key_nonce');
    
    if (!publicKeyStr || !encryptedSecretKeyStr || !nonceStr) {
      return null;
    }
    
    try {
      // Derive key from password
      const passwordHash = this.hash(password);
      const derivedKey = util.decodeBase64(passwordHash).slice(0, nacl.secretbox.keyLength);
      
      // Decrypt secret key
      const encryptedSecretKey = util.decodeBase64(encryptedSecretKeyStr);
      const nonce = util.decodeBase64(nonceStr);
      const secretKey = nacl.secretbox.open(encryptedSecretKey, nonce, derivedKey);
      
      if (!secretKey) {
        throw new Error('Failed to decrypt secret key');
      }
      
      const publicKey = util.decodeBase64(publicKeyStr);
      
      this.keyPair = {
        publicKey,
        secretKey,
      };
      
      return {
        publicKey: publicKeyStr,
        secretKey: util.encodeBase64(secretKey),
      };
    } catch (error) {
      console.error('Failed to load keys:', error);
      return null;
    }
  }
  
  /**
   * Clear stored keys
   */
  clearStoredKeys(): void {
    localStorage.removeItem('aethex_public_key');
    localStorage.removeItem('aethex_encrypted_secret_key');
    localStorage.removeItem('aethex_key_nonce');
    this.keyPair = null;
  }
}
