// EN: Unit tests for @goodgo/auth-sdk — JWT utilities.
// VI: Unit tests cho @goodgo/auth-sdk — các tiện ích JWT.
import { describe, it, expect } from 'vitest';
import {
  createToken,
  verifyToken,
  decodeToken,
  isTokenExpired,
  extractTokenFromHeader,
} from './index';

const SECRET = 'test-secret-goodgo-platform';

// ---------------------------------------------------------------------------
// createToken
// ---------------------------------------------------------------------------
describe('createToken', () => {
  it('should return a non-empty JWT string', () => {
    const token = createToken({ userId: 'u-1', email: 'a@b.com', role: 'owner' }, SECRET);
    expect(typeof token).toBe('string');
    expect(token.split('.')).toHaveLength(3);
  });

  it('should embed payload fields in the token', () => {
    const token = createToken({ userId: 'u-42', email: 'merchant@goodgo.vn', role: 'staff' }, SECRET);
    const decoded = decodeToken(token);
    expect(decoded?.userId).toBe('u-42');
    expect(decoded?.email).toBe('merchant@goodgo.vn');
    expect(decoded?.role).toBe('staff');
  });

  it('should respect custom expiresIn parameter', () => {
    const token = createToken({ userId: 'u-1', email: 'a@b.com', role: 'owner' }, SECRET, '1h');
    const decoded = decodeToken(token);
    expect(decoded?.exp).toBeDefined();
    // EN: exp should be ~1 hour from now
    // VI: exp phải cách hiện tại ~1 giờ
    const diff = (decoded!.exp! * 1000) - Date.now();
    expect(diff).toBeGreaterThan(3500 * 1000);
    expect(diff).toBeLessThan(3700 * 1000);
  });
});

// ---------------------------------------------------------------------------
// verifyToken
// ---------------------------------------------------------------------------
describe('verifyToken', () => {
  it('should return decoded payload for valid token', () => {
    const token = createToken({ userId: 'u-1', email: 'a@b.com', role: 'admin' }, SECRET);
    const payload = verifyToken(token, { secret: SECRET });
    expect(payload.userId).toBe('u-1');
    expect(payload.role).toBe('admin');
  });

  it('should throw for wrong secret', () => {
    const token = createToken({ userId: 'u-1', email: 'a@b.com', role: 'admin' }, SECRET);
    expect(() => verifyToken(token, { secret: 'wrong-secret' })).toThrow(
      /Invalid or expired token/
    );
  });

  it('should throw for expired token (default ignoreExpiration=false)', () => {
    const token = createToken({ userId: 'u-1', email: 'a@b.com', role: 'admin' }, SECRET, '0s');
    // EN: Expired immediately; small sleep not needed — exp is in the past after signing.
    // VI: Hết hạn ngay lập tức.
    expect(() => verifyToken(token, { secret: SECRET })).toThrow();
  });

  it('should not throw for expired token when ignoreExpiration=true', () => {
    const token = createToken({ userId: 'u-1', email: 'a@b.com', role: 'admin' }, SECRET, '0s');
    const payload = verifyToken(token, { secret: SECRET, ignoreExpiration: true });
    expect(payload.userId).toBe('u-1');
  });

  it('should throw for malformed token', () => {
    expect(() => verifyToken('not.a.valid.jwt', { secret: SECRET })).toThrow();
  });
});

// ---------------------------------------------------------------------------
// decodeToken
// ---------------------------------------------------------------------------
describe('decodeToken', () => {
  it('should decode without verifying signature', () => {
    const token = createToken({ userId: 'u-99', email: 'x@y.com', role: 'user' }, SECRET);
    const payload = decodeToken(token);
    expect(payload?.userId).toBe('u-99');
  });

  it('should return null for garbage input', () => {
    const result = decodeToken('garbage');
    // EN: jwt.decode returns null for bad input — we propagate null.
    // VI: jwt.decode trả null cho input sai — ta propagate null.
    expect(result).toBeNull();
  });
});

// ---------------------------------------------------------------------------
// isTokenExpired
// ---------------------------------------------------------------------------
describe('isTokenExpired', () => {
  it('should return false for a freshly created token', () => {
    const token = createToken({ userId: 'u-1', email: 'a@b.com', role: 'admin' }, SECRET, '15m');
    expect(isTokenExpired(token)).toBe(false);
  });

  it('should return true for an expired token', () => {
    const token = createToken({ userId: 'u-1', email: 'a@b.com', role: 'admin' }, SECRET, '0s');
    expect(isTokenExpired(token)).toBe(true);
  });

  it('should return true for a malformed token', () => {
    expect(isTokenExpired('not-a-jwt')).toBe(true);
  });

  it('should return true for an empty string', () => {
    expect(isTokenExpired('')).toBe(true);
  });
});

// ---------------------------------------------------------------------------
// extractTokenFromHeader
// ---------------------------------------------------------------------------
describe('extractTokenFromHeader', () => {
  it('should extract token from valid Bearer header', () => {
    const token = createToken({ userId: 'u-1', email: 'a@b.com', role: 'admin' }, SECRET);
    const extracted = extractTokenFromHeader(`Bearer ${token}`);
    expect(extracted).toBe(token);
  });

  it('should return null when header is undefined', () => {
    expect(extractTokenFromHeader(undefined)).toBeNull();
  });

  it('should return null for non-Bearer scheme', () => {
    expect(extractTokenFromHeader('Basic dXNlcjpwYXNz')).toBeNull();
  });

  it('should return null for malformed header (single part)', () => {
    expect(extractTokenFromHeader('BearerTokenWithoutSpace')).toBeNull();
  });

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