import crypto from 'crypto';

import { logger } from '@goodgo/logger';

import { appConfig } from '../../config/app.config';

/**
 * EN: Encryption service for sensitive data at rest
 * VI: Service mã hóa cho dữ liệu nhạy cảm khi lưu trữ
 */
export class EncryptionService {
  private algorithm = 'aes-256-gcm';
  private keyLength = 32; // 256 bits
  private ivLength = 16; // 128 bits for GCM
  private tagLength = 16; // 128 bits authentication tag

  /**
   * EN: Get encryption key from config
   * VI: Lấy key mã hóa từ config
   */
  private getKey(): Buffer {
    const key = appConfig.encryptionKey;
    if (!key || key === 'default-encryption-key-change-in-production') {
      throw new Error('ENCRYPTION_KEY must be configured and not use default value');
    }


    // If key is 64 chars hex (32 bytes), parse it
    if (key.length === 64 && /^[0-9a-fA-F]+$/.test(key)) {
      return Buffer.from(key, 'hex');
    }

    // If key is shorter than required, hash it
    if (key.length < this.keyLength) {
      return crypto.scryptSync(key, 'salt', this.keyLength);
    }

    // If key is longer (and not hex 64), truncate it
    if (key.length > this.keyLength) {
      return Buffer.from(key.slice(0, this.keyLength));
    }

    return Buffer.from(key);
  }

  /**
   * EN: Encrypt sensitive data
   * VI: Mã hóa dữ liệu nhạy cảm
   */
  encrypt(plainText: string): string {
    try {
      const key = this.getKey();
      const iv = crypto.randomBytes(this.ivLength);
      const cipher = crypto.createCipheriv(this.algorithm, key, iv) as crypto.CipherGCM;
      cipher.setAAD(Buffer.from('iam-service')); // Additional authenticated data

      let encrypted = cipher.update(plainText, 'utf8', 'hex');
      encrypted += cipher.final('hex');

      const authTag = cipher.getAuthTag();

      // Format: iv:authTag:encryptedData
      const result = `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;

      logger.debug('Data encrypted successfully');
      return result;
    } catch (error) {
      logger.error('Encryption failed', { error });
      throw new Error('Failed to encrypt data');
    }
  }

  /**
   * EN: Decrypt sensitive data
   * VI: Giải mã dữ liệu nhạy cảm
   */
  decrypt(encryptedText: string): string {
    try {
      const key = this.getKey();
      const parts = encryptedText.split(':');

      if (parts.length !== 3) {
        throw new Error('Invalid encrypted data format');
      }

      const iv = Buffer.from(parts[0], 'hex');
      const authTag = Buffer.from(parts[1], 'hex');
      const encrypted = parts[2];

      const decipher = crypto.createDecipheriv(this.algorithm, key, iv) as crypto.DecipherGCM;
      decipher.setAAD(Buffer.from('iam-service')); // Same AAD as encryption
      decipher.setAuthTag(authTag);

      let decrypted = decipher.update(encrypted, 'hex', 'utf8');
      decrypted += decipher.final('utf8');

      logger.debug('Data decrypted successfully');
      return decrypted;
    } catch (error) {
      logger.error('Decryption failed', { error });
      throw new Error('Failed to decrypt data');
    }
  }

  /**
   * EN: Check if data is encrypted (has the expected format)
   * VI: Kiểm tra dữ liệu có được mã hóa không (có định dạng mong đợi)
   */
  isEncrypted(data: string): boolean {
    const parts = data.split(':');
    return parts.length === 3 &&
      parts[0].length === this.ivLength * 2 && // IV in hex
      parts[1].length === this.tagLength * 2 && // Auth tag in hex
      parts[2].length > 0; // Encrypted data
  }

  /**
   * EN: Encrypt if not already encrypted
   * VI: Mã hóa nếu chưa được mã hóa
   */
  encryptIfNeeded(data: string): string {
    if (!data || this.isEncrypted(data)) {
      return data;
    }
    return this.encrypt(data);
  }

  /**
   * EN: Decrypt if encrypted
   * VI: Giải mã nếu đã được mã hóa
   */
  decryptIfNeeded(data: string): string {
    if (!data || !this.isEncrypted(data)) {
      return data;
    }
    return this.decrypt(data);
  }

  /**
   * EN: Hash data using SHA-256 (for tokens)
   * VI: Hash dữ liệu sử dụng SHA-256 (cho tokens)
   * 
   * @param data - Data to hash / Dữ liệu cần hash
   * @returns SHA-256 hash (hex) / SHA-256 hash (hex)
   */
  hash(data: string): string {
    try {
      return crypto.createHash('sha256').update(data).digest('hex');
    } catch (error) {
      logger.error('Hashing failed', { error });
      throw new Error('Failed to hash data');
    }
  }

  /**
   * EN: Compare hash with plaintext data using timing-safe comparison
   * VI: So sánh hash với plaintext data sử dụng timing-safe comparison
   * 
   * @param data - Plaintext data / Dữ liệu plaintext
   * @param hash - Hash to compare / Hash để so sánh
   * @returns True if match / True nếu khớp
   */
  compareHash(data: string, hash: string): boolean {
    try {
      const dataHash = this.hash(data);
      // EN: Use timing-safe comparison to prevent timing attacks
      // VI: Sử dụng timing-safe comparison để ngăn timing attacks
      return crypto.timingSafeEqual(
        Buffer.from(dataHash, 'hex'),
        Buffer.from(hash, 'hex')
      );
    } catch (error) {
      logger.error('Hash comparison failed', { error });
      return false;
    }
  }
}

export const encryptionService = new EncryptionService();