import { CredentialEncryptionService } from './credential-encryption.service';

describe('CredentialEncryptionService', () => {
  let service: CredentialEncryptionService;
  let mockConfig: any;

  beforeEach(() => {
    mockConfig = {
      get: jest.fn().mockReturnValue('test-encryption-key-32bytes!!'),
    };
    service = new CredentialEncryptionService(mockConfig as any);
  });

  // ═══════════════════════════════════════════════════════════════════
  // encrypt / decrypt round-trip
  // ═══════════════════════════════════════════════════════════════════

  describe('encrypt ↔ decrypt', () => {
    it('round-trip: encrypt then decrypt returns original plaintext', () => {
      const plaintext = 'sk-test-api-key-12345678';
      const encrypted = service.encrypt(plaintext);
      const decrypted = service.decrypt(encrypted);
      expect(decrypted).toBe(plaintext);
    });

    it('produces different ciphertext for same plaintext (random IV)', () => {
      const plaintext = 'sk-mytest';
      const c1 = service.encrypt(plaintext);
      const c2 = service.encrypt(plaintext);
      expect(c1).not.toBe(c2);
    });

    it('encrypted output is base64', () => {
      const c = service.encrypt('hello');
      expect(() => Buffer.from(c, 'base64')).not.toThrow();
    });

    it('handles empty string', () => {
      const c = service.encrypt('');
      const d = service.decrypt(c);
      expect(d).toBe('');
    });

    it('handles unicode characters', () => {
      const plaintext = '密钥测试-key-中文';
      const c = service.encrypt(plaintext);
      const d = service.decrypt(c);
      expect(d).toBe(plaintext);
    });
  });

  // ═══════════════════════════════════════════════════════════════════
  // hash
  // ═══════════════════════════════════════════════════════════════════

  describe('hash', () => {
    it('returns deterministic hex hash for same input', () => {
      const h1 = service.hash('sk-abc');
      const h2 = service.hash('sk-abc');
      expect(h1).toBe(h2);
      expect(h1).toHaveLength(64); // SHA-256 = 64 hex chars
    });

    it('produces different hashes for different inputs', () => {
      const h1 = service.hash('sk-abc');
      const h2 = service.hash('sk-def');
      expect(h1).not.toBe(h2);
    });

    it('handles empty string', () => {
      const h = service.hash('');
      expect(h).toHaveLength(64);
    });
  });

  // ═══════════════════════════════════════════════════════════════════
  // mask
  // ═══════════════════════════════════════════════════════════════════

  describe('mask', () => {
    it('masks long keys: first-4 + **** + last-4', () => {
      const masked = service.mask('sk-1234567890abcdef');
      expect(masked).toBe('sk-1****cdef');
    });

    it('masks short keys: first-2 + ****', () => {
      const masked = service.mask('sk-abcd');
      expect(masked).toBe('sk****');
    });

    it('handles exactly 8 chars as short path', () => {
      const masked = service.mask('12345678');
      expect(masked).toBe('12****');
    });

    it('handles 9 chars as long path', () => {
      const masked = service.mask('123456789');
      expect(masked).toBe('1234****6789');
    });
  });

  // ═══════════════════════════════════════════════════════════════════
  // redact
  // ═══════════════════════════════════════════════════════════════════

  describe('redact', () => {
    it('replaces sk- prefixed tokens in log messages', () => {
      const msg = 'Calling API with key sk-1234567890abc and secret sk-abcdefghijklmno';
      const redacted = service.redact(msg);
      expect(redacted).not.toContain('sk-1234567890abc');
      expect(redacted).not.toContain('sk-abcdefghijklmno');
      expect(redacted).toContain('***REDACTED***');
    });

    it('does not modify messages without API keys', () => {
      const msg = 'User u1 created job j1 successfully';
      expect(service.redact(msg)).toBe(msg);
    });

    it('does not redact short sk- strings (< 10 chars after prefix)', () => {
      const msg = 'sk-short is not a key';
      expect(service.redact(msg)).toBe(msg);
    });

    it('handles empty string', () => {
      expect(service.redact('')).toBe('');
    });
  });

  // ═══════════════════════════════════════════════════════════════════
  // getEncryptionKey error
  // ═══════════════════════════════════════════════════════════════════

  describe('missing config', () => {
    it('throws when CREDENTIAL_ENCRYPTION_KEY is not configured', () => {
      mockConfig.get.mockReturnValue(null);
      expect(() => service.encrypt('foo')).toThrow('CREDENTIAL_ENCRYPTION_KEY not configured');
      expect(() => service.decrypt('foo')).toThrow('CREDENTIAL_ENCRYPTION_KEY not configured');
    });
  });
});
