/**
 * Unit Tests for Encryption Utilities
 *
 * Tests password hashing, API key generation, HMAC signatures, and content hashing
 */

import {
  hashPassword,
  verifyPassword,
  generateApiKey,
  hashApiKey,
  generateToken,
  generateWebhookSignature,
  verifyWebhookSignature,
  generateContentHash,
  generateWebhookSecret,
} from '../../../utils/encryption';

describe('Encryption Utils', () => {
  describe('hashPassword', () => {
    it('should hash a password', async () => {
      const password = 'MySecurePassword123!';
      const hash = await hashPassword(password);

      expect(hash).toBeDefined();
      expect(hash).not.toBe(password);
      expect(hash.length).toBeGreaterThan(50); // bcrypt hashes are ~60 chars
      expect(hash).toMatch(/^\$2[aby]\$\d{2}\$/); // bcrypt format
    });

    it('should generate different hashes for same password', async () => {
      const password = 'MySecurePassword123!';
      const hash1 = await hashPassword(password);
      const hash2 = await hashPassword(password);

      // Bcrypt uses salt, so same password produces different hashes
      expect(hash1).not.toBe(hash2);
    });

    it('should handle empty password', async () => {
      const password = '';
      const hash = await hashPassword(password);

      expect(hash).toBeDefined();
      expect(hash.length).toBeGreaterThan(0);
    });

    it('should handle special characters', async () => {
      const password = '!@#$%^&*()_+-=[]{}|;:,.<>?';
      const hash = await hashPassword(password);

      expect(hash).toBeDefined();
      expect(hash.length).toBeGreaterThan(50);
    });
  });

  describe('verifyPassword', () => {
    it('should verify correct password', async () => {
      const password = 'MySecurePassword123!';
      const hash = await hashPassword(password);
      const isValid = await verifyPassword(password, hash);

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

    it('should reject incorrect password', async () => {
      const password = 'MySecurePassword123!';
      const wrongPassword = 'WrongPassword456!';
      const hash = await hashPassword(password);
      const isValid = await verifyPassword(wrongPassword, hash);

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

    it('should reject empty password against hash', async () => {
      const password = 'MySecurePassword123!';
      const hash = await hashPassword(password);
      const isValid = await verifyPassword('', hash);

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

    it('should handle case sensitivity', async () => {
      const password = 'MySecurePassword123!';
      const hash = await hashPassword(password);
      const isValid = await verifyPassword('mysecurepassword123!', hash);

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

  describe('generateApiKey', () => {
    it('should generate API key with default prefix', () => {
      const result = generateApiKey();

      expect(result.key).toMatch(/^sk_live_[a-f0-9]{64}$/);
      expect(result.hash).toBeDefined();
      expect(result.hash.length).toBe(64); // SHA-256 produces 64 hex chars
      expect(result.prefix).toBe(result.key.substring(0, 16));
      expect(result.prefix.startsWith('sk_live_')).toBe(true);
    });

    it('should generate API key with custom prefix', () => {
      const result = generateApiKey('sk_test');

      expect(result.key).toMatch(/^sk_test_[a-f0-9]{64}$/);
      expect(result.prefix).toBe(result.key.substring(0, 16));
      expect(result.prefix.startsWith('sk_test_')).toBe(true);
    });

    it('should generate unique keys', () => {
      const result1 = generateApiKey();
      const result2 = generateApiKey();

      expect(result1.key).not.toBe(result2.key);
      expect(result1.hash).not.toBe(result2.hash);
    });

    it('should generate deterministic hash for same key', () => {
      const result = generateApiKey();
      const hash1 = hashApiKey(result.key);
      const hash2 = hashApiKey(result.key);

      expect(hash1).toBe(hash2);
      expect(hash1).toBe(result.hash);
    });
  });

  describe('hashApiKey', () => {
    it('should hash API key using SHA-256', () => {
      const apiKey = 'sk_live_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
      const hash = hashApiKey(apiKey);

      expect(hash).toBeDefined();
      expect(hash.length).toBe(64); // SHA-256 = 64 hex chars
      expect(hash).toMatch(/^[a-f0-9]{64}$/);
    });

    it('should produce consistent hashes', () => {
      const apiKey = 'sk_live_test123';
      const hash1 = hashApiKey(apiKey);
      const hash2 = hashApiKey(apiKey);

      expect(hash1).toBe(hash2);
    });

    it('should produce different hashes for different keys', () => {
      const key1 = 'sk_live_test123';
      const key2 = 'sk_live_test456';
      const hash1 = hashApiKey(key1);
      const hash2 = hashApiKey(key2);

      expect(hash1).not.toBe(hash2);
    });

    it('should be case sensitive', () => {
      const key1 = 'sk_live_TEST123';
      const key2 = 'sk_live_test123';
      const hash1 = hashApiKey(key1);
      const hash2 = hashApiKey(key2);

      expect(hash1).not.toBe(hash2);
    });
  });

  describe('generateToken', () => {
    it('should generate token with default length', () => {
      const token = generateToken();

      expect(token).toBeDefined();
      expect(token.length).toBe(64); // 32 bytes = 64 hex chars
      expect(token).toMatch(/^[a-f0-9]{64}$/);
    });

    it('should generate token with custom length', () => {
      const token = generateToken(16);

      expect(token.length).toBe(32); // 16 bytes = 32 hex chars
      expect(token).toMatch(/^[a-f0-9]{32}$/);
    });

    it('should generate unique tokens', () => {
      const token1 = generateToken();
      const token2 = generateToken();

      expect(token1).not.toBe(token2);
    });

    it('should handle small length', () => {
      const token = generateToken(4);

      expect(token.length).toBe(8); // 4 bytes = 8 hex chars
      expect(token).toMatch(/^[a-f0-9]{8}$/);
    });
  });

  describe('generateWebhookSignature', () => {
    it('should generate HMAC signature', () => {
      const timestamp = '1234567890';
      const jobId = 'job_123';
      const status = 'completed';
      const secret = 'webhook_secret_key';

      const signature = generateWebhookSignature(timestamp, jobId, status, secret);

      expect(signature).toBeDefined();
      expect(signature.length).toBe(64); // HMAC-SHA256 = 64 hex chars
      expect(signature).toMatch(/^[a-f0-9]{64}$/);
    });

    it('should generate consistent signatures for same input', () => {
      const timestamp = '1234567890';
      const jobId = 'job_123';
      const status = 'completed';
      const secret = 'webhook_secret_key';

      const sig1 = generateWebhookSignature(timestamp, jobId, status, secret);
      const sig2 = generateWebhookSignature(timestamp, jobId, status, secret);

      expect(sig1).toBe(sig2);
    });

    it('should generate different signatures for different inputs', () => {
      const secret = 'webhook_secret_key';

      const sig1 = generateWebhookSignature('1234567890', 'job_123', 'completed', secret);
      const sig2 = generateWebhookSignature('1234567890', 'job_456', 'completed', secret);

      expect(sig1).not.toBe(sig2);
    });

    it('should generate different signatures for different secrets', () => {
      const timestamp = '1234567890';
      const jobId = 'job_123';
      const status = 'completed';

      const sig1 = generateWebhookSignature(timestamp, jobId, status, 'secret1');
      const sig2 = generateWebhookSignature(timestamp, jobId, status, 'secret2');

      expect(sig1).not.toBe(sig2);
    });
  });

  describe('verifyWebhookSignature', () => {
    it('should verify valid signature', () => {
      const timestamp = '1234567890';
      const jobId = 'job_123';
      const status = 'completed';
      const secret = 'webhook_secret_key';

      const signature = generateWebhookSignature(timestamp, jobId, status, secret);
      const isValid = verifyWebhookSignature(timestamp, jobId, status, secret, signature);

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

    it('should reject invalid signature', () => {
      const timestamp = '1234567890';
      const jobId = 'job_123';
      const status = 'completed';
      const secret = 'webhook_secret_key';

      const validSignature = generateWebhookSignature(timestamp, jobId, status, secret);
      const invalidSignature = `${validSignature[0] === '0' ? '1' : '0'}${validSignature.slice(1)}`;

      const isValid = verifyWebhookSignature(timestamp, jobId, status, secret, invalidSignature);

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

    it('should reject signature with wrong secret', () => {
      const timestamp = '1234567890';
      const jobId = 'job_123';
      const status = 'completed';

      const signature = generateWebhookSignature(timestamp, jobId, status, 'secret1');
      const isValid = verifyWebhookSignature(timestamp, jobId, status, 'secret2', signature);

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

    it('should reject signature with modified timestamp', () => {
      const timestamp = '1234567890';
      const jobId = 'job_123';
      const status = 'completed';
      const secret = 'webhook_secret_key';

      const signature = generateWebhookSignature(timestamp, jobId, status, secret);
      const isValid = verifyWebhookSignature('9999999999', jobId, status, secret, signature);

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

  describe('generateContentHash', () => {
    it('should generate content hash', () => {
      const content = 'Hello world';
      const sourceLang = 'en';
      const targetLang = 'fr';

      const hash = generateContentHash(content, sourceLang, targetLang);

      expect(hash).toBeDefined();
      expect(hash.length).toBe(64); // SHA-256 = 64 hex chars
      expect(hash).toMatch(/^[a-f0-9]{64}$/);
    });

    it('should generate consistent hashes for same input', () => {
      const hash1 = generateContentHash('Hello', 'en', 'fr');
      const hash2 = generateContentHash('Hello', 'en', 'fr');

      expect(hash1).toBe(hash2);
    });

    it('should generate different hashes for different content', () => {
      const hash1 = generateContentHash('Hello', 'en', 'fr');
      const hash2 = generateContentHash('World', 'en', 'fr');

      expect(hash1).not.toBe(hash2);
    });

    it('should generate different hashes for different languages', () => {
      const hash1 = generateContentHash('Hello', 'en', 'fr');
      const hash2 = generateContentHash('Hello', 'en', 'es');

      expect(hash1).not.toBe(hash2);
    });

    it('should handle empty content', () => {
      const hash = generateContentHash('', 'en', 'fr');

      expect(hash).toBeDefined();
      expect(hash.length).toBe(64);
    });
  });

  describe('generateWebhookSecret', () => {
    it('should generate webhook secret', () => {
      const secret = generateWebhookSecret();

      expect(secret).toBeDefined();
      expect(secret.length).toBe(64); // 32 bytes = 64 hex chars
      expect(secret).toMatch(/^[a-f0-9]{64}$/);
    });

    it('should generate unique secrets', () => {
      const secret1 = generateWebhookSecret();
      const secret2 = generateWebhookSecret();

      expect(secret1).not.toBe(secret2);
    });

    it('should generate cryptographically random secrets', () => {
      const secrets = new Set<string>();
      for (let i = 0; i < 100; i++) {
        secrets.add(generateWebhookSecret());
      }

      // All 100 secrets should be unique
      expect(secrets.size).toBe(100);
    });
  });
});
