import { MfaService } from '../../infrastructure/services/mfa.service';

describe('MfaService', () => {
  let mfaService: MfaService;

  beforeEach(() => {
    mfaService = new MfaService();
  });

  describe('generateSetup', () => {
    it('generates a secret, otpauth URL, and QR code data URL', async () => {
      const result = await mfaService.generateSetup('user@example.com');

      expect(result.secret).toBeDefined();
      expect(result.secret.length).toBeGreaterThan(0);
      expect(result.otpauthUrl).toContain('otpauth://totp/');
      expect(result.otpauthUrl).toContain('GoodGo%20Platform');
      expect(result.otpauthUrl).toContain('user%40example.com');
      expect(result.qrCodeDataUrl).toMatch(/^data:image\/png;base64,/);
    });

    it('generates unique secrets on each call', async () => {
      const result1 = await mfaService.generateSetup('user@example.com');
      const result2 = await mfaService.generateSetup('user@example.com');

      expect(result1.secret).not.toEqual(result2.secret);
    });
  });

  describe('verifyTotp', () => {
    it('returns false for an invalid code', async () => {
      const setup = await mfaService.generateSetup('user@example.com');
      const isValid = await mfaService.verifyTotp('000000', setup.secret);

      // This will almost always be false — a random code is extremely unlikely to match
      expect(typeof isValid).toBe('boolean');
    });

    it('returns false for empty code', async () => {
      const setup = await mfaService.generateSetup('user@example.com');
      const isValid = await mfaService.verifyTotp('', setup.secret);

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

  describe('generateBackupCodes', () => {
    it('generates 10 backup codes', () => {
      const result = mfaService.generateBackupCodes();

      expect(result.plainCodes).toHaveLength(10);
      expect(result.hashedCodes).toHaveLength(10);
    });

    it('generates unique codes', () => {
      const result = mfaService.generateBackupCodes();
      const uniqueCodes = new Set(result.plainCodes);

      expect(uniqueCodes.size).toBe(10);
    });

    it('generates 8-character alphanumeric codes', () => {
      const result = mfaService.generateBackupCodes();

      for (const code of result.plainCodes) {
        expect(code.length).toBe(8);
        expect(code).toMatch(/^[A-Z2-9]+$/);
      }
    });

    it('hashed codes are hex strings', () => {
      const result = mfaService.generateBackupCodes();

      for (const hash of result.hashedCodes) {
        expect(hash).toMatch(/^[a-f0-9]{64}$/); // SHA-256 = 64 hex chars
      }
    });
  });

  describe('verifyBackupCode', () => {
    it('verifies a valid backup code and returns its index', () => {
      const result = mfaService.generateBackupCodes();
      const firstCode = result.plainCodes[0]!;

      const index = mfaService.verifyBackupCode(firstCode, result.hashedCodes);

      expect(index).toBe(0);
    });

    it('returns -1 for invalid backup code', () => {
      const result = mfaService.generateBackupCodes();

      const index = mfaService.verifyBackupCode('INVALID!', result.hashedCodes);

      expect(index).toBe(-1);
    });

    it('normalizes code by removing spaces and dashes', () => {
      const result = mfaService.generateBackupCodes();
      const firstCode = result.plainCodes[0]!;

      // Add spaces and dashes
      const codeWithSpaces = `${firstCode.slice(0, 4)} ${firstCode.slice(4)}`;
      const index = mfaService.verifyBackupCode(codeWithSpaces, result.hashedCodes);

      expect(index).toBe(0);
    });

    it('is case insensitive', () => {
      const result = mfaService.generateBackupCodes();
      const firstCode = result.plainCodes[0]!;

      const index = mfaService.verifyBackupCode(firstCode.toLowerCase(), result.hashedCodes);

      expect(index).toBe(0);
    });

    it('verifies the correct code among multiple', () => {
      const result = mfaService.generateBackupCodes();
      const fifthCode = result.plainCodes[4]!;

      const index = mfaService.verifyBackupCode(fifthCode, result.hashedCodes);

      expect(index).toBe(4);
    });
  });
});
