/**
 * Authentication Tests
 */
import { describe, it, expect, beforeEach, vi } from 'vitest';
import jwt from 'jsonwebtoken';

describe('Authentication', () => {
  const testSecret = 'test-secret-key-123';

  describe('JWT Token Generation', () => {
    it('should generate a valid JWT token', () => {
      const payload = { userId: 'user-123', email: 'test@example.com' };
      const token = jwt.sign(payload, testSecret, { expiresIn: '1h' });

      expect(typeof token).toBe('string');
      expect(token.split('.')).toHaveLength(3);
    });

    it('should include user ID in token', () => {
      const userId = 'test-user-456';
      const token = jwt.sign({ userId }, testSecret);

      const decoded = jwt.verify(token, testSecret) as { userId: string };

      expect(decoded.userId).toBe(userId);
    });

    it('should set expiration time', () => {
      const token = jwt.sign({ userId: 'test' }, testSecret, { expiresIn: '1h' });

      const decoded = jwt.verify(token, testSecret) as { exp: number };

      expect(decoded.exp).toBeDefined();
      expect(decoded.exp).toBeGreaterThan(Math.floor(Date.now() / 1000));
    });
  });

  describe('JWT Token Verification', () => {
    it('should verify valid token', () => {
      const payload = { userId: 'user-123' };
      const token = jwt.sign(payload, testSecret);

      const decoded = jwt.verify(token, testSecret) as { userId: string };

      expect(decoded.userId).toBe('user-123');
    });

    it('should reject invalid token', () => {
      const token = 'invalid.token.here';

      expect(() => jwt.verify(token, testSecret)).toThrow();
    });

    it('should reject expired token', () => {
      const token = jwt.sign({ userId: 'test' }, testSecret, { expiresIn: '-1h' });

      expect(() => jwt.verify(token, testSecret)).toThrow();
    });

    it('should reject token with wrong secret', () => {
      const token = jwt.sign({ userId: 'test' }, testSecret);

      expect(() => jwt.verify(token, 'wrong-secret')).toThrow();
    });

    it('should reject tampered token', () => {
      const token = jwt.sign({ userId: 'test' }, testSecret);
      const [header, payload, signature] = token.split('.');
      const tamperedToken = `${header}.${payload}modified.${signature}`;

      expect(() => jwt.verify(tamperedToken, testSecret)).toThrow();
    });
  });

  describe('Token Payload Validation', () => {
    it('should include required claims', () => {
      const payload = {
        userId: 'user-123',
        email: 'user@example.com',
        role: 'player',
      };

      const token = jwt.sign(payload, testSecret);
      const decoded = jwt.verify(token, testSecret) as typeof payload;

      expect(decoded.userId).toBe('user-123');
      expect(decoded.email).toBe('user@example.com');
      expect(decoded.role).toBe('player');
    });

    it('should handle missing optional claims', () => {
      const payload = { userId: 'user-123' };

      const token = jwt.sign(payload, testSecret);
      const decoded = jwt.verify(token, testSecret) as typeof payload & { email?: string };

      expect(decoded.userId).toBe('user-123');
      expect(decoded.email).toBeUndefined();
    });
  });

  describe('Token Refresh', () => {
    it('should generate new token with same user', () => {
      const userId = 'user-123';
      const originalToken = jwt.sign({ userId, nonce: 'original' }, testSecret, { expiresIn: '1h' });

      // Simulate refresh - add a different nonce to ensure tokens are different
      const decoded = jwt.verify(originalToken, testSecret) as { userId: string };
      const newToken = jwt.sign({ userId: decoded.userId, nonce: 'refreshed' }, testSecret, { expiresIn: '1h' });

      const newDecoded = jwt.verify(newToken, testSecret) as { userId: string };

      expect(newDecoded.userId).toBe(userId);
      expect(newToken).not.toBe(originalToken);
    });
  });

  describe('Anonymous Sessions', () => {
    it('should generate anonymous session token', () => {
      const sessionId = `anon-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
      const token = jwt.sign({ sessionId, anonymous: true }, testSecret, { expiresIn: '24h' });

      const decoded = jwt.verify(token, testSecret) as { sessionId: string; anonymous: boolean };

      expect(decoded.anonymous).toBe(true);
      expect(decoded.sessionId).toBe(sessionId);
    });

    it('should differentiate anonymous from authenticated', () => {
      const anonToken = jwt.sign({ sessionId: 'anon-1', anonymous: true }, testSecret);
      const authToken = jwt.sign({ userId: 'user-1', anonymous: false }, testSecret);

      const anonDecoded = jwt.verify(anonToken, testSecret) as { anonymous: boolean };
      const authDecoded = jwt.verify(authToken, testSecret) as { anonymous: boolean };

      expect(anonDecoded.anonymous).toBe(true);
      expect(authDecoded.anonymous).toBe(false);
    });
  });

  describe('Token Security', () => {
    it('should use HS256 algorithm by default', () => {
      const token = jwt.sign({ userId: 'test' }, testSecret);
      const header = JSON.parse(Buffer.from(token.split('.')[0], 'base64').toString());

      expect(header.alg).toBe('HS256');
    });

    it('should not expose sensitive data in payload', () => {
      const payload = {
        userId: 'user-123',
        email: 'user@example.com',
        // Password should NOT be included
      };

      const token = jwt.sign(payload, testSecret);
      const decoded = jwt.verify(token, testSecret) as any;

      expect(decoded.password).toBeUndefined();
    });
  });

  describe('Session Management', () => {
    it('should track session creation time', () => {
      const token = jwt.sign(
        { userId: 'test', createdAt: Date.now() },
        testSecret
      );

      const decoded = jwt.verify(token, testSecret) as { createdAt: number };

      expect(decoded.createdAt).toBeDefined();
      expect(decoded.createdAt).toBeLessThanOrEqual(Date.now());
    });

    it('should include issued at (iat) claim', () => {
      const token = jwt.sign({ userId: 'test' }, testSecret);
      const decoded = jwt.verify(token, testSecret) as { iat: number };

      expect(decoded.iat).toBeDefined();
    });
  });

  describe('Multi-device Support', () => {
    it('should generate unique tokens for same user', () => {
      const userId = 'user-123';

      const token1 = jwt.sign({ userId, deviceId: 'device-1' }, testSecret);
      const token2 = jwt.sign({ userId, deviceId: 'device-2' }, testSecret);

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

      const decoded1 = jwt.verify(token1, testSecret) as { deviceId: string };
      const decoded2 = jwt.verify(token2, testSecret) as { deviceId: string };

      expect(decoded1.deviceId).toBe('device-1');
      expect(decoded2.deviceId).toBe('device-2');
    });
  });

  describe('Error Handling', () => {
    it('should handle malformed token gracefully', () => {
      const malformed = 'not-a-valid-jwt';

      expect(() => jwt.verify(malformed, testSecret)).toThrow();
    });

    it('should handle null token', () => {
      expect(() => jwt.verify(null as any, testSecret)).toThrow();
    });

    it('should handle empty token', () => {
      expect(() => jwt.verify('', testSecret)).toThrow();
    });

    it('should handle undefined secret', () => {
      const token = jwt.sign({ userId: 'test' }, testSecret);

      expect(() => jwt.verify(token, undefined as any)).toThrow();
    });
  });
});
