/**
 * Unit Tests for JWT Service
 *
 * Tests JWT token generation, verification, and extraction
 */

import {
  generateAccessToken,
  generateRefreshToken,
  verifyAccessToken,
  verifyRefreshToken,
  extractTokenFromHeader,
} from '../../../auth/jwtService';
import jwt from 'jsonwebtoken';
import { config } from '../../../config';

describe('JWT Service', () => {
  const mockUserId = 'user_123';
  const mockEmail = 'test@example.com';
  const mockPlan = 'professional';
  const mockSubscriptionStatus = 'active';

  describe('generateAccessToken', () => {
    it('should generate valid access token', () => {
      const token = generateAccessToken(mockUserId, mockEmail, mockPlan, mockSubscriptionStatus);

      expect(token).toBeDefined();
      expect(typeof token).toBe('string');
      expect(token.split('.')).toHaveLength(3); // JWT has 3 parts
    });

    it('should include user data in token payload', () => {
      const token = generateAccessToken(mockUserId, mockEmail, mockPlan, mockSubscriptionStatus);
      const decoded = jwt.decode(token) as any;

      expect(decoded.userId).toBe(mockUserId);
      expect(decoded.email).toBe(mockEmail);
      expect(decoded.plan).toBe(mockPlan);
      expect(decoded.subscriptionStatus).toBe(mockSubscriptionStatus);
      expect(decoded.type).toBe('access');
    });

    it('should include issuer and audience claims', () => {
      const token = generateAccessToken(mockUserId, mockEmail, mockPlan, mockSubscriptionStatus);
      const decoded = jwt.decode(token) as any;

      expect(decoded.iss).toBe('translate.press.zone');
      expect(decoded.aud).toBe('api');
    });

    it('should include expiration time', () => {
      const token = generateAccessToken(mockUserId, mockEmail, mockPlan, mockSubscriptionStatus);
      const decoded = jwt.decode(token) as any;

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

    it('should generate different tokens for different users', () => {
      const token1 = generateAccessToken('user_1', 'user1@test.com', mockPlan, mockSubscriptionStatus);
      const token2 = generateAccessToken('user_2', 'user2@test.com', mockPlan, mockSubscriptionStatus);

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

  describe('generateRefreshToken', () => {
    it('should generate valid refresh token', () => {
      const token = generateRefreshToken(mockUserId);

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

    it('should include minimal payload', () => {
      const token = generateRefreshToken(mockUserId);
      const decoded = jwt.decode(token) as any;

      expect(decoded.userId).toBe(mockUserId);
      expect(decoded.type).toBe('refresh');
      expect(decoded.email).toBeUndefined(); // Should not include extra data
    });

    it('should have longer expiration than access token', () => {
      const accessToken = generateAccessToken(mockUserId, mockEmail, mockPlan, mockSubscriptionStatus);
      const refreshToken = generateRefreshToken(mockUserId);

      const accessDecoded = jwt.decode(accessToken) as any;
      const refreshDecoded = jwt.decode(refreshToken) as any;

      expect(refreshDecoded.exp).toBeGreaterThan(accessDecoded.exp);
    });

    it('should generate different tokens each time', () => {
      jest.useFakeTimers();
      try {
        const token1 = generateRefreshToken(mockUserId);
        jest.advanceTimersByTime(1001);
        const token2 = generateRefreshToken(mockUserId);

        expect(token1).not.toBe(token2);
      } finally {
        jest.useRealTimers();
      }
    });
  });

  describe('verifyAccessToken', () => {
    it('should verify valid access token', () => {
      const token = generateAccessToken(mockUserId, mockEmail, mockPlan, mockSubscriptionStatus);
      const payload = verifyAccessToken(token);

      expect(payload.userId).toBe(mockUserId);
      expect(payload.email).toBe(mockEmail);
      expect(payload.plan).toBe(mockPlan);
      expect(payload.subscriptionStatus).toBe(mockSubscriptionStatus);
      expect(payload.type).toBe('access');
    });

    it('should reject expired token', () => {
      // Create token with immediate expiration
      const expiredToken = jwt.sign(
        { userId: mockUserId, email: mockEmail, type: 'access' },
        config.jwtAccessSecret,
        { expiresIn: '-1s', issuer: 'translate.press.zone', audience: 'api' }
      );

      expect(() => verifyAccessToken(expiredToken)).toThrow('Access token expired');
    });

    it('should reject token with invalid signature', () => {
      const token = generateAccessToken(mockUserId, mockEmail, mockPlan, mockSubscriptionStatus);
      const tamperedToken = token.slice(0, -10) + 'tampered123';

      expect(() => verifyAccessToken(tamperedToken)).toThrow('Invalid access token');
    });

    it('should reject token with wrong type', () => {
      const refreshToken = generateRefreshToken(mockUserId);

      // Cross-secret signature check fails before the `type` claim is read.
      expect(() => verifyAccessToken(refreshToken)).toThrow('Invalid access token');
    });

    it('should reject malformed token', () => {
      expect(() => verifyAccessToken('not.a.valid.jwt')).toThrow();
    });

    it('should reject empty token', () => {
      expect(() => verifyAccessToken('')).toThrow();
    });
  });

  describe('verifyRefreshToken', () => {
    it('should verify valid refresh token', () => {
      const token = generateRefreshToken(mockUserId);
      const payload = verifyRefreshToken(token);

      expect(payload.userId).toBe(mockUserId);
      expect(payload.type).toBe('refresh');
    });

    it('should reject expired refresh token', () => {
      const expiredToken = jwt.sign(
        { userId: mockUserId, type: 'refresh' },
        config.jwtRefreshSecret,
        { expiresIn: '-1s', issuer: 'translate.press.zone', audience: 'api' }
      );

      expect(() => verifyRefreshToken(expiredToken)).toThrow('Refresh token expired');
    });

    it('should reject token with invalid signature', () => {
      const token = generateRefreshToken(mockUserId);
      const tamperedToken = token.slice(0, -10) + 'tampered123';

      expect(() => verifyRefreshToken(tamperedToken)).toThrow('Invalid refresh token');
    });

    it('should reject access token as refresh token', () => {
      const accessToken = generateAccessToken(mockUserId, mockEmail, mockPlan, mockSubscriptionStatus);

      expect(() => verifyRefreshToken(accessToken)).toThrow();
    });
  });

  describe('extractTokenFromHeader', () => {
    it('should extract token from valid Authorization header', () => {
      const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
      const authHeader = `Bearer ${token}`;

      const extracted = extractTokenFromHeader(authHeader);

      expect(extracted).toBe(token);
    });

    it('should return null for missing header', () => {
      const extracted = extractTokenFromHeader(undefined);

      expect(extracted).toBeNull();
    });

    it('should return null for header without Bearer prefix', () => {
      const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
      const authHeader = token;

      const extracted = extractTokenFromHeader(authHeader);

      expect(extracted).toBeNull();
    });

    it('should return null for malformed Bearer header', () => {
      const authHeader = 'Bearer';

      const extracted = extractTokenFromHeader(authHeader);

      expect(extracted).toBeNull();
    });

    it('should return null for wrong authentication scheme', () => {
      const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
      const authHeader = `Basic ${token}`;

      const extracted = extractTokenFromHeader(authHeader);

      expect(extracted).toBeNull();
    });

    it('should return null for empty header', () => {
      const extracted = extractTokenFromHeader('');

      expect(extracted).toBeNull();
    });

    it('should reject header with extra spaces', () => {
      const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
      const authHeader = `Bearer  ${token}`; // Double space

      const extracted = extractTokenFromHeader(authHeader);

      expect(extracted).toBeNull();
    });
  });

  describe('Token integration', () => {
    it('should support full access token flow', () => {
      // Generate token
      const token = generateAccessToken(mockUserId, mockEmail, mockPlan, mockSubscriptionStatus);

      // Extract from header
      const authHeader = `Bearer ${token}`;
      const extracted = extractTokenFromHeader(authHeader);

      // Verify token
      const payload = verifyAccessToken(extracted!);

      expect(payload.userId).toBe(mockUserId);
      expect(payload.email).toBe(mockEmail);
    });

    it('should support full refresh token flow', () => {
      // Generate token
      const token = generateRefreshToken(mockUserId);

      // Extract from header
      const authHeader = `Bearer ${token}`;
      const extracted = extractTokenFromHeader(authHeader);

      // Verify token
      const payload = verifyRefreshToken(extracted!);

      expect(payload.userId).toBe(mockUserId);
    });

    it('should maintain user identity through token renewal', () => {
      // Generate initial access token
      const initialToken = generateAccessToken(mockUserId, mockEmail, mockPlan, mockSubscriptionStatus);
      const initialPayload = verifyAccessToken(initialToken);

      // Generate new token for same user (simulating refresh)
      const renewedToken = generateAccessToken(
        initialPayload.userId,
        initialPayload.email,
        initialPayload.plan,
        initialPayload.subscriptionStatus
      );
      const renewedPayload = verifyAccessToken(renewedToken);

      expect(renewedPayload.userId).toBe(initialPayload.userId);
      expect(renewedPayload.email).toBe(initialPayload.email);
    });
  });
});
