/**
 * Share Encryption Domain Service Unit Tests
 */

import { ShareEncryptionDomainService } from '../../../src/domain/services/share-encryption.domain-service';
import { ShareData } from '../../../src/domain/value-objects';

describe('ShareEncryptionDomainService', () => {
  let service: ShareEncryptionDomainService;

  beforeEach(() => {
    service = new ShareEncryptionDomainService();
  });

  describe('encrypt/decrypt', () => {
    it('should encrypt and decrypt data correctly', () => {
      const rawData = Buffer.from('This is the raw share data to encrypt');
      const masterKey = service.generateMasterKey();

      const encrypted = service.encrypt(rawData, masterKey);
      const decrypted = service.decrypt(encrypted, masterKey);

      expect(decrypted).toEqual(rawData);
    });

    it('should produce different ciphertexts for same plaintext', () => {
      const rawData = Buffer.from('Same plaintext data');
      const masterKey = service.generateMasterKey();

      const encrypted1 = service.encrypt(rawData, masterKey);
      const encrypted2 = service.encrypt(rawData, masterKey);

      // IV is random, so ciphertexts should be different
      expect(encrypted1.encryptedData).not.toEqual(encrypted2.encryptedData);
      expect(encrypted1.iv).not.toEqual(encrypted2.iv);
    });

    it('should fail decryption with wrong key', () => {
      const rawData = Buffer.from('Secret data');
      const correctKey = service.generateMasterKey();
      const wrongKey = service.generateMasterKey();

      const encrypted = service.encrypt(rawData, correctKey);

      expect(() => service.decrypt(encrypted, wrongKey)).toThrow();
    });

    it('should fail decryption with tampered ciphertext', () => {
      const rawData = Buffer.from('Secret data');
      const masterKey = service.generateMasterKey();

      const encrypted = service.encrypt(rawData, masterKey);

      // Tamper with the ciphertext
      const tamperedData = Buffer.from(encrypted.encryptedData);
      tamperedData[0] ^= 0xFF;
      const tampered = ShareData.create(tamperedData, encrypted.iv, encrypted.authTag);

      expect(() => service.decrypt(tampered, masterKey)).toThrow();
    });

    it('should fail decryption with tampered authTag', () => {
      const rawData = Buffer.from('Secret data');
      const masterKey = service.generateMasterKey();

      const encrypted = service.encrypt(rawData, masterKey);

      // Tamper with the auth tag
      const tamperedTag = Buffer.from(encrypted.authTag);
      tamperedTag[0] ^= 0xFF;
      const tampered = ShareData.create(encrypted.encryptedData, encrypted.iv, tamperedTag);

      expect(() => service.decrypt(tampered, masterKey)).toThrow();
    });
  });

  describe('generateMasterKey', () => {
    it('should generate 32-byte key', () => {
      const key = service.generateMasterKey();
      expect(key).toHaveLength(32);
    });

    it('should generate unique keys', () => {
      const key1 = service.generateMasterKey();
      const key2 = service.generateMasterKey();
      expect(key1).not.toEqual(key2);
    });
  });

  describe('deriveKeyFromPassword', () => {
    it('should derive consistent key from password', () => {
      const password = 'my-secure-password';
      const salt = service.generateSalt();

      const key1 = service.deriveKeyFromPassword(password, salt);
      const key2 = service.deriveKeyFromPassword(password, salt);

      expect(key1).toEqual(key2);
      expect(key1).toHaveLength(32);
    });

    it('should derive different keys for different passwords', () => {
      const salt = service.generateSalt();

      const key1 = service.deriveKeyFromPassword('password1', salt);
      const key2 = service.deriveKeyFromPassword('password2', salt);

      expect(key1).not.toEqual(key2);
    });

    it('should derive different keys for different salts', () => {
      const password = 'my-password';
      const salt1 = service.generateSalt();
      const salt2 = service.generateSalt();

      const key1 = service.deriveKeyFromPassword(password, salt1);
      const key2 = service.deriveKeyFromPassword(password, salt2);

      expect(key1).not.toEqual(key2);
    });

    it('should throw error for empty password', () => {
      const salt = service.generateSalt();
      expect(() => service.deriveKeyFromPassword('', salt)).toThrow();
    });

    it('should throw error for short salt', () => {
      expect(() => service.deriveKeyFromPassword('password', Buffer.alloc(8))).toThrow();
    });
  });

  describe('validateMasterKey', () => {
    it('should throw error for invalid key length', () => {
      const rawData = Buffer.from('data');
      const shortKey = Buffer.alloc(16);

      expect(() => service.encrypt(rawData, shortKey)).toThrow();
    });
  });

  describe('computeHmac/verifyHmac', () => {
    it('should compute and verify HMAC', () => {
      const data = Buffer.from('data to authenticate');
      const key = service.generateMasterKey();

      const hmac = service.computeHmac(data, key);
      const isValid = service.verifyHmac(data, key, hmac);

      expect(isValid).toBe(true);
    });

    it('should fail verification with wrong key', () => {
      const data = Buffer.from('data to authenticate');
      const correctKey = service.generateMasterKey();
      const wrongKey = service.generateMasterKey();

      const hmac = service.computeHmac(data, correctKey);
      const isValid = service.verifyHmac(data, wrongKey, hmac);

      expect(isValid).toBe(false);
    });

    it('should fail verification with tampered data', () => {
      const data = Buffer.from('original data');
      const key = service.generateMasterKey();

      const hmac = service.computeHmac(data, key);
      const tamperedData = Buffer.from('tampered data');
      const isValid = service.verifyHmac(tamperedData, key, hmac);

      expect(isValid).toBe(false);
    });
  });
});
