import { log } from '../utils/logger';

/**
 * Client-side password encryption service
 * Uses Web Crypto API with AES-256-GCM algorithm
 */
class EncryptionService {
  private encryptionKey: CryptoKey | null = null;

  /**
   * Initialize encryption key
   * @param keyBase64 Base64 encoded key from server
   */
  async initializeKey(keyBase64: string): Promise<void> {
    try {
      // this._keyBase64 = keyBase64;
      
      // Decode Base64 key
      const keyBuffer = this.base64ToArrayBuffer(keyBase64);
      
      // Import key for use with Web Crypto API
      this.encryptionKey = await crypto.subtle.importKey(
        'raw',
        keyBuffer,
        { name: 'AES-GCM' },
        false, // not exportable
        ['encrypt', 'decrypt']
      );

      log.info('encryption-service', 'Encryption key initialized successfully');
    } catch (error) {
      log.error('encryption-service', 'Failed to initialize encryption key', { error });
      throw new Error('Failed to initialize encryption key');
    }
  }

  /**
   * Encrypt password using AES-GCM and AEAD
   * @param password Password in plain text
   * @param sessionId Session ID for AAD
   * @returns Encrypted password in Base64
   */
  async encryptPassword(password: string, sessionId?: string): Promise<{encrypted: string, nonce: string, timestamp: number}> {
    if (!this.encryptionKey) {
      throw new Error('Encryption key not initialized');
    }

    try {
      // CRITICAL: Generate unique IV (12 bytes for GCM)
      const iv = crypto.getRandomValues(new Uint8Array(12));
      
      // Convert password to ArrayBuffer
      const passwordBuffer = new TextEncoder().encode(password);
      
      // AAD used ONLY for saved machines (when sessionId exists)
      // For temporary connection passwords AAD is NOT needed
      const encryptParams: AesGcmParams = {
        name: 'AES-GCM',
        iv: iv
      };
      
      let timestamp = 0;
      let clientNonce: Uint8Array | undefined;
      
      // If sessionId provided - use AAD for additional security
      if (sessionId) {
        timestamp = Date.now();
        clientNonce = crypto.getRandomValues(new Uint8Array(16));
        const aadData = {
          sessionId: sessionId,
          timestamp: timestamp,
          clientNonce: Array.from(clientNonce)
        };
        encryptParams.additionalData = new TextEncoder().encode(JSON.stringify(aadData));
      }
      
      // Encrypt
      const encryptedBuffer = await crypto.subtle.encrypt(
        encryptParams,
        this.encryptionKey,
        passwordBuffer
      );

      // Combine IV + encrypted data
      const combined = new Uint8Array(iv.length + encryptedBuffer.byteLength);
      combined.set(iv, 0);
      combined.set(new Uint8Array(encryptedBuffer), iv.length);

      // Convert to Base64
      const encrypted = this.arrayBufferToBase64(combined.buffer);
      const nonce = clientNonce ? this.arrayBufferToBase64(clientNonce.buffer as ArrayBuffer) : '';

      log.debug('encryption-service', 'Password encrypted successfully', {
        passwordLength: password.length,
        encryptedLength: encrypted.length,
        sessionId: sessionId,
        usesAAD: !!sessionId
      });

      return {
        encrypted,
        nonce,
        timestamp
      };
    } catch (error) {
      log.error('encryption-service', 'Failed to encrypt password', { error });
      throw new Error('Password encryption failed');
    }
  }

  /**
   * Decrypt password (for testing)
   * @param encryptedPassword Base64 encrypted string
   * @param sessionId Session ID for AAD (optional - for testing)
   * @param nonce Client nonce for AAD (optional - for testing)
   * @param timestamp Timestamp for AAD (optional - for testing)
   * @returns Password in plain text
   */
  async decryptPassword(
    encryptedPassword: string, 
    sessionId?: string, 
    nonce?: string, 
    timestamp?: number
  ): Promise<string> {
    if (!this.encryptionKey) {
      throw new Error('Encryption key not initialized');
    }

    try {
      // Decode Base64
      const combined = this.base64ToArrayBuffer(encryptedPassword);
      
      // Extract IV (first 12 bytes) and encrypted data
      const iv = combined.slice(0, 12);
      const encryptedData = combined.slice(12);

      // FIXED: Create AAD if parameters provided
      let additionalData: Uint8Array | undefined = undefined;
      if (sessionId && nonce && timestamp) {
        const clientNonceArray = new Uint8Array(this.base64ToArrayBuffer(nonce));
        const aadData = {
          sessionId: sessionId,
          timestamp: timestamp,
          clientNonce: Array.from(clientNonceArray)
        };
        additionalData = new TextEncoder().encode(JSON.stringify(aadData));
      }

      // Decrypt
      const decryptedBuffer = await crypto.subtle.decrypt(
        {
          name: 'AES-GCM',
          iv: iv,
          ...(additionalData && { additionalData })  // Add AAD if exists
        },
        this.encryptionKey,
        encryptedData
      );

      // Convert back to string
      const password = new TextDecoder().decode(decryptedBuffer);

      log.debug('encryption-service', 'Password decrypted successfully');

      return password;
    } catch (error) {
      log.error('encryption-service', 'Failed to decrypt password', { error });
      throw new Error('Password decryption failed');
    }
  }

  /**
   * Check if encryption key is initialized
   */
  isInitialized(): boolean {
    return this.encryptionKey !== null;
  }

  /**
   * Get current encryption status
   */
  getStatus(): {
    initialized: boolean;
  } {
    return {
      initialized: this.isInitialized(),
    };
  }

  /**
   * Clear encryption key
   */
  clearKey(): void {
    this.encryptionKey = null;
    log.info('encryption-service', 'Encryption key cleared');
  }

  /**
   * Convert Base64 string to ArrayBuffer
   */
  private base64ToArrayBuffer(base64: string): ArrayBuffer {
    const binaryString = atob(base64);
    const bytes = new Uint8Array(binaryString.length);
    for (let i = 0; i < binaryString.length; i++) {
      bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes.buffer;
  }

  /**
   * Convert ArrayBuffer to Base64 string
   */
  private arrayBufferToBase64(buffer: ArrayBuffer): string {
    const bytes = new Uint8Array(buffer);
    let binary = '';
    for (let i = 0; i < bytes.byteLength; i++) {
      binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary);
  }

  /**
   * Test encryption/decryption
   */
  async testEncryption(testPassword: string = 'test123', sessionId: string = 'test-session'): Promise<boolean> {
    try {
      if (!this.isInitialized()) {
        throw new Error('Encryption key not initialized');
      }

      // Encrypt test password
      const encryptionResult = await this.encryptPassword(testPassword, sessionId);
      
      // FIXED: Decrypt with same AAD parameters
      const decrypted = await this.decryptPassword(
        encryptionResult.encrypted, 
        sessionId,
        encryptionResult.nonce,
        encryptionResult.timestamp
      );
      
      // Check that we got the same password
      const success = decrypted === testPassword;
      
      log.info('encryption-service', 'Encryption test completed', {
        success,
        testPasswordLength: testPassword.length,
        encryptedLength: encryptionResult.encrypted.length
      });

      return success;
    } catch (error) {
      log.error('encryption-service', 'Encryption test failed', { error });
      return false;
    }
  }
}

// Export singleton instance
export const encryptionService = new EncryptionService();
export default encryptionService; 