import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { AesEncryptionService } from '../../../src/infrastructure/crypto/aes-encryption.service';

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

  const testKey = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef';
  const testKeyId = 'test-key-v1';

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AesEncryptionService,
        {
          provide: ConfigService,
          useValue: {
            get: (key: string) => {
              switch (key) {
                case 'BACKUP_ENCRYPTION_KEY':
                  return testKey;
                case 'BACKUP_ENCRYPTION_KEY_ID':
                  return testKeyId;
                default:
                  return undefined;
              }
            },
          },
        },
      ],
    }).compile();

    service = module.get<AesEncryptionService>(AesEncryptionService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('encrypt', () => {
    it('should encrypt plaintext successfully', async () => {
      const plaintext = 'Hello, World!';

      const result = await service.encrypt(plaintext);

      expect(result.encrypted).toBeDefined();
      expect(result.keyId).toBe(testKeyId);
      expect(result.encrypted).not.toBe(plaintext);
      // Should contain 3 parts separated by ':'
      expect(result.encrypted.split(':').length).toBe(3);
    });

    it('should produce different ciphertext for same plaintext (due to random IV)', async () => {
      const plaintext = 'Same message';

      const result1 = await service.encrypt(plaintext);
      const result2 = await service.encrypt(plaintext);

      expect(result1.encrypted).not.toBe(result2.encrypted);
    });
  });

  describe('decrypt', () => {
    it('should decrypt ciphertext back to original plaintext', async () => {
      const plaintext = 'Secret message for testing';

      const { encrypted, keyId } = await service.encrypt(plaintext);
      const decrypted = await service.decrypt(encrypted, keyId);

      expect(decrypted).toBe(plaintext);
    });

    it('should handle special characters', async () => {
      const plaintext = 'Special chars: !@#$%^&*()_+-={}[]|\\:";\'<>?,./';

      const { encrypted, keyId } = await service.encrypt(plaintext);
      const decrypted = await service.decrypt(encrypted, keyId);

      expect(decrypted).toBe(plaintext);
    });

    it('should handle unicode characters', async () => {
      const plaintext = 'Unicode: 你好世界 🌍 مرحبا';

      const { encrypted, keyId } = await service.encrypt(plaintext);
      const decrypted = await service.decrypt(encrypted, keyId);

      expect(decrypted).toBe(plaintext);
    });

    it('should handle large payloads', async () => {
      const plaintext = 'a'.repeat(10000);

      const { encrypted, keyId } = await service.encrypt(plaintext);
      const decrypted = await service.decrypt(encrypted, keyId);

      expect(decrypted).toBe(plaintext);
    });

    it('should throw error for invalid format', async () => {
      await expect(service.decrypt('invalid-format', testKeyId)).rejects.toThrow(
        'Invalid encrypted data format',
      );
    });

    it('should throw error for non-existent key', async () => {
      const { encrypted } = await service.encrypt('test');

      await expect(service.decrypt(encrypted, 'non-existent-key')).rejects.toThrow(
        'Decryption key not found',
      );
    });
  });

  describe('addKey', () => {
    it('should add new key for rotation', async () => {
      const newKeyId = 'key-v2';
      const newKey = 'fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210';

      service.addKey(newKeyId, newKey);

      // Encrypt with original key
      const { encrypted: encrypted1 } = await service.encrypt('test');

      // Should still be able to decrypt (uses current key)
      const decrypted = await service.decrypt(encrypted1, testKeyId);
      expect(decrypted).toBe('test');
    });

    it('should throw error for invalid key length', () => {
      expect(() => service.addKey('bad-key', 'too-short')).toThrow(
        'Key must be 256 bits',
      );
    });
  });

  describe('getCurrentKeyId', () => {
    it('should return current key ID', () => {
      expect(service.getCurrentKeyId()).toBe(testKeyId);
    });
  });
});
