/**
 * Share Encryption Domain Service
 *
 * Handles encryption and decryption of share data using AES-256-GCM.
 * This is a domain service because encryption is a core domain concern for MPC.
 */

import * as crypto from 'crypto';
import { ShareData } from '../value-objects';

export class ShareEncryptionDomainService {
  private readonly algorithm = 'aes-256-gcm';
  private readonly keyLength = 32; // 256 bits
  private readonly ivLength = 12;  // 96 bits for GCM
  private readonly authTagLength = 16; // 128 bits

  /**
   * Encrypt raw share data
   *
   * @param rawShareData - The raw share data from TSS-lib
   * @param masterKey - The master encryption key (32 bytes)
   * @returns Encrypted ShareData value object
   */
  encrypt(rawShareData: Buffer, masterKey: Buffer): ShareData {
    this.validateMasterKey(masterKey);

    // Generate random IV
    const iv = crypto.randomBytes(this.ivLength);

    // Create cipher
    const cipher = crypto.createCipheriv(this.algorithm, masterKey, iv, {
      authTagLength: this.authTagLength,
    });

    // Encrypt data
    const encrypted = Buffer.concat([
      cipher.update(rawShareData),
      cipher.final(),
    ]);

    // Get authentication tag
    const authTag = cipher.getAuthTag();

    return ShareData.create(encrypted, iv, authTag);
  }

  /**
   * Decrypt share data
   *
   * @param shareData - The encrypted ShareData value object
   * @param masterKey - The master encryption key (32 bytes)
   * @returns Decrypted raw share data
   */
  decrypt(shareData: ShareData, masterKey: Buffer): Buffer {
    this.validateMasterKey(masterKey);

    // Create decipher
    const decipher = crypto.createDecipheriv(
      this.algorithm,
      masterKey,
      shareData.iv,
      { authTagLength: this.authTagLength },
    );

    // Set authentication tag
    decipher.setAuthTag(shareData.authTag);

    // Decrypt data
    try {
      const decrypted = Buffer.concat([
        decipher.update(shareData.encryptedData),
        decipher.final(),
      ]);
      return decrypted;
    } catch (error) {
      throw new Error('Failed to decrypt share data: authentication failed');
    }
  }

  /**
   * Derive a key from password using PBKDF2
   * Used for development/testing environments
   *
   * @param password - The password to derive key from
   * @param salt - The salt for key derivation
   * @returns Derived key (32 bytes)
   */
  deriveKeyFromPassword(password: string, salt: Buffer): Buffer {
    if (!password || password.length === 0) {
      throw new Error('Password cannot be empty');
    }
    if (!salt || salt.length < 16) {
      throw new Error('Salt must be at least 16 bytes');
    }

    return crypto.pbkdf2Sync(
      password,
      salt,
      100000, // iterations
      this.keyLength,
      'sha256',
    );
  }

  /**
   * Generate a random master key
   *
   * @returns Random 32-byte key
   */
  generateMasterKey(): Buffer {
    return crypto.randomBytes(this.keyLength);
  }

  /**
   * Generate a random salt
   *
   * @param length - Salt length in bytes (default 32)
   * @returns Random salt
   */
  generateSalt(length: number = 32): Buffer {
    return crypto.randomBytes(length);
  }

  /**
   * Validate master key format
   */
  private validateMasterKey(key: Buffer): void {
    if (!key || key.length !== this.keyLength) {
      throw new Error(`Master key must be ${this.keyLength} bytes`);
    }
  }

  /**
   * Compute HMAC-SHA256 of data
   * Useful for integrity verification
   */
  computeHmac(data: Buffer, key: Buffer): Buffer {
    return crypto.createHmac('sha256', key).update(data).digest();
  }

  /**
   * Verify HMAC
   */
  verifyHmac(data: Buffer, key: Buffer, expectedHmac: Buffer): boolean {
    const computedHmac = this.computeHmac(data, key);
    return crypto.timingSafeEqual(computedHmac, expectedHmac);
  }
}
