import {
  generateAccessToken,
  generateRefreshToken,
  generateAuthTokens,
  verifyAccessToken,
  verifyRefreshToken,
  hashPassword,
  comparePassword,
  validatePassword,
  extractTokenFromHeader,
  decodeToken
} from '../auth';
// Config is mocked below, so we don't need to import it

// Mock the config
jest.mock('../../config/env', () => ({
  config: {
    jwt: {
      secret: 'test-secret',
      refreshSecret: 'test-refresh-secret',
      expiresIn: '1h',
      refreshExpiresIn: '7d'
    },
    security: {
      bcryptRounds: 10
    }
  }
}));

// Mock logger
jest.mock('../logger', () => ({
  info: jest.fn(),
  error: jest.fn()
}));

describe('Auth Utilities', () => {
  const mockPayload = {
    userId: '123e4567-e89b-12d3-a456-426614174000',
    email: 'test@example.com',
    role: 'user'
  };

  describe('generateAccessToken', () => {
    it('should generate a valid access token', () => {
      const token = generateAccessToken(mockPayload);
      
      expect(token).toBeDefined();
      expect(typeof token).toBe('string');
      expect(token.split('.')).toHaveLength(3); // JWT has 3 parts
    });

    it('should include the correct payload in the token', () => {
      const token = generateAccessToken(mockPayload);
      const decoded = decodeToken(token);
      
      expect(decoded).toMatchObject({
        userId: mockPayload.userId,
        email: mockPayload.email,
        role: mockPayload.role,
        iss: 'cim-processor',
        aud: 'cim-processor-users'
      });
    });
  });

  describe('generateRefreshToken', () => {
    it('should generate a valid refresh token', () => {
      const token = generateRefreshToken(mockPayload);
      
      expect(token).toBeDefined();
      expect(typeof token).toBe('string');
      expect(token.split('.')).toHaveLength(3);
    });

    it('should use refresh secret for signing', () => {
      const token = generateRefreshToken(mockPayload);
      const decoded = decodeToken(token);
      
      expect(decoded).toMatchObject({
        userId: mockPayload.userId,
        email: mockPayload.email,
        role: mockPayload.role
      });
    });
  });

  describe('generateAuthTokens', () => {
    it('should generate both access and refresh tokens', () => {
      const tokens = generateAuthTokens(mockPayload);
      
      expect(tokens).toHaveProperty('accessToken');
      expect(tokens).toHaveProperty('refreshToken');
      expect(tokens).toHaveProperty('expiresIn');
      expect(typeof tokens.accessToken).toBe('string');
      expect(typeof tokens.refreshToken).toBe('string');
      expect(typeof tokens.expiresIn).toBe('number');
    });

    it('should calculate correct expiration time', () => {
      const tokens = generateAuthTokens(mockPayload);
      
      // 1h = 3600 seconds
      expect(tokens.expiresIn).toBe(3600);
    });
  });

  describe('verifyAccessToken', () => {
    it('should verify a valid access token', () => {
      const token = generateAccessToken(mockPayload);
      const decoded = verifyAccessToken(token);
      
      expect(decoded).toMatchObject({
        userId: mockPayload.userId,
        email: mockPayload.email,
        role: mockPayload.role
      });
    });

    it('should throw error for invalid token', () => {
      expect(() => {
        verifyAccessToken('invalid-token');
      }).toThrow('Invalid or expired access token');
    });

    it('should throw error for token signed with wrong secret', () => {
      const token = generateRefreshToken(mockPayload); // Uses refresh secret
      
      expect(() => {
        verifyAccessToken(token); // Expects access secret
      }).toThrow('Invalid or expired access token');
    });
  });

  describe('verifyRefreshToken', () => {
    it('should verify a valid refresh token', () => {
      const token = generateRefreshToken(mockPayload);
      const decoded = verifyRefreshToken(token);
      
      expect(decoded).toMatchObject({
        userId: mockPayload.userId,
        email: mockPayload.email,
        role: mockPayload.role
      });
    });

    it('should throw error for invalid refresh token', () => {
      expect(() => {
        verifyRefreshToken('invalid-token');
      }).toThrow('Invalid or expired refresh token');
    });
  });

  describe('hashPassword', () => {
    it('should hash password correctly', async () => {
      const password = 'TestPassword123!';
      const hashedPassword = await hashPassword(password);
      
      expect(hashedPassword).toBeDefined();
      expect(typeof hashedPassword).toBe('string');
      expect(hashedPassword).not.toBe(password);
      expect(hashedPassword.startsWith('$2a$') || hashedPassword.startsWith('$2b$')).toBe(true); // bcrypt format
    });

    it('should generate different hashes for same password', async () => {
      const password = 'TestPassword123!';
      const hash1 = await hashPassword(password);
      const hash2 = await hashPassword(password);
      
      expect(hash1).not.toBe(hash2);
    });
  });

  describe('comparePassword', () => {
    it('should return true for correct password', async () => {
      const password = 'TestPassword123!';
      const hashedPassword = await hashPassword(password);
      const isMatch = await comparePassword(password, hashedPassword);
      
      expect(isMatch).toBe(true);
    });

    it('should return false for incorrect password', async () => {
      const password = 'TestPassword123!';
      const wrongPassword = 'WrongPassword123!';
      const hashedPassword = await hashPassword(password);
      const isMatch = await comparePassword(wrongPassword, hashedPassword);
      
      expect(isMatch).toBe(false);
    });
  });

  describe('validatePassword', () => {
    it('should validate a strong password', () => {
      const password = 'StrongPass123!';
      const result = validatePassword(password);
      
      expect(result.isValid).toBe(true);
      expect(result.errors).toHaveLength(0);
    });

    it('should reject password that is too short', () => {
      const password = 'Short1!';
      const result = validatePassword(password);
      
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Password must be at least 8 characters long');
    });

    it('should reject password without uppercase letter', () => {
      const password = 'lowercase123!';
      const result = validatePassword(password);
      
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Password must contain at least one uppercase letter');
    });

    it('should reject password without lowercase letter', () => {
      const password = 'UPPERCASE123!';
      const result = validatePassword(password);
      
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Password must contain at least one lowercase letter');
    });

    it('should reject password without number', () => {
      const password = 'NoNumbers!';
      const result = validatePassword(password);
      
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Password must contain at least one number');
    });

    it('should reject password without special character', () => {
      const password = 'NoSpecialChar123';
      const result = validatePassword(password);
      
      expect(result.isValid).toBe(false);
      expect(result.errors).toContain('Password must contain at least one special character');
    });

    it('should return all validation errors for weak password', () => {
      const password = 'weak';
      const result = validatePassword(password);
      
      expect(result.isValid).toBe(false);
      expect(result.errors).toHaveLength(4); // 'weak' has lowercase, so only 4 errors
      expect(result.errors).toContain('Password must be at least 8 characters long');
      expect(result.errors).toContain('Password must contain at least one uppercase letter');
      expect(result.errors).toContain('Password must contain at least one number');
      expect(result.errors).toContain('Password must contain at least one special character');
    });
  });

  describe('extractTokenFromHeader', () => {
    it('should extract token from valid Authorization header', () => {
      const header = 'Bearer valid-token-here';
      const token = extractTokenFromHeader(header);
      
      expect(token).toBe('valid-token-here');
    });

    it('should return null for missing header', () => {
      const token = extractTokenFromHeader(undefined);
      
      expect(token).toBeNull();
    });

    it('should return null for empty header', () => {
      const token = extractTokenFromHeader('');
      
      expect(token).toBeNull();
    });

    it('should return null for invalid format', () => {
      const token = extractTokenFromHeader('InvalidFormat token');
      
      expect(token).toBeNull();
    });

    it('should return null for missing token part', () => {
      const token = extractTokenFromHeader('Bearer ');
      
      expect(token).toBeNull();
    });
  });

  describe('decodeToken', () => {
    it('should decode a valid token', () => {
      const token = generateAccessToken(mockPayload);
      const decoded = decodeToken(token);
      
      expect(decoded).toMatchObject({
        userId: mockPayload.userId,
        email: mockPayload.email,
        role: mockPayload.role
      });
    });

    it('should return null for invalid token', () => {
      const decoded = decodeToken('invalid-token');
      
      expect(decoded).toBeNull();
    });
  });
}); 