import { describe, expect, it, beforeAll, afterAll } from 'vitest';
import { createJwtUtils, hashPassword, verifyPassword } from '../index.js';

describe('JWT utilities', () => {
  const SECRET = 'test-jwt-secret-at-least-32-chars-long!!';

  beforeAll(() => {
    process.env.JWT_SECRET = SECRET;
  });

  afterAll(() => {
    delete process.env.JWT_SECRET;
  });

  it('creates and verifies an access token', async () => {
    const jwt = createJwtUtils({ issuer: 'test-issuer' });
    const token = await jwt.createAccessToken({
      sub: 'user-1',
      email: 'test@example.com',
      role: 'admin',
    });

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

    const payload = await jwt.verifyToken(token);
    expect(payload).not.toBeNull();
    expect(payload!.sub).toBe('user-1');
    expect(payload!.email).toBe('test@example.com');
    expect(payload!.role).toBe('admin');
    expect(payload!.type).toBe('access');
  });

  it('creates and verifies a refresh token', async () => {
    const jwt = createJwtUtils({ issuer: 'test-issuer' });
    const token = await jwt.createRefreshToken({ sub: 'user-1' });

    const payload = await jwt.verifyToken(token);
    expect(payload).not.toBeNull();
    expect(payload!.sub).toBe('user-1');
    expect(payload!.type).toBe('refresh');
  });

  it('returns null for invalid token', async () => {
    const jwt = createJwtUtils({ issuer: 'test-issuer' });
    const result = await jwt.verifyToken('garbage.not.valid');
    expect(result).toBeNull();
  });

  it('returns null for wrong issuer', async () => {
    const jwt1 = createJwtUtils({ issuer: 'issuer-a' });
    const jwt2 = createJwtUtils({ issuer: 'issuer-b' });

    const token = await jwt1.createAccessToken({
      sub: 'u1',
      email: 'a@b.com',
      role: 'user',
    });

    const result = await jwt2.verifyToken(token);
    expect(result).toBeNull();
  });

  it('sets productId from payload or defaults to issuer', async () => {
    const jwt = createJwtUtils({ issuer: 'lysnrai' });

    const t1 = await jwt.createAccessToken({
      sub: 'u1',
      email: 'a@b.com',
      role: 'user',
    });
    const p1 = await jwt.verifyToken(t1);
    expect(p1!.productId).toBe('lysnrai');

    const t2 = await jwt.createAccessToken({
      sub: 'u1',
      email: 'a@b.com',
      role: 'user',
      productId: 'mindlyst',
    });
    const p2 = await jwt.verifyToken(t2);
    expect(p2!.productId).toBe('mindlyst');
  });

  it('respects custom expiry', async () => {
    const jwt = createJwtUtils({
      issuer: 'test',
      accessTokenExpiry: '2h',
      refreshTokenExpiry: '7d',
    });

    const access = await jwt.createAccessToken({
      sub: 'u1',
      email: 'a@b.com',
      role: 'user',
    });
    const refresh = await jwt.createRefreshToken({ sub: 'u1' });

    expect(typeof access).toBe('string');
    expect(typeof refresh).toBe('string');
  });

  it('throws when JWT_SECRET is not set', async () => {
    const origSecret = process.env.JWT_SECRET;
    delete process.env.JWT_SECRET;

    const jwt = createJwtUtils({ issuer: 'test' });
    await expect(
      jwt.createAccessToken({ sub: 'u1', email: 'a@b.com', role: 'user' })
    ).rejects.toThrow('JWT_SECRET must be set');

    process.env.JWT_SECRET = origSecret;
  });
});

describe('password hashing', () => {
  it('hashes a password and verifies it', async () => {
    const hash = await hashPassword('MySecret123!');
    expect(typeof hash).toBe('string');
    expect(hash).not.toBe('MySecret123!');

    const valid = await verifyPassword('MySecret123!', hash);
    expect(valid).toBe(true);
  });

  it('rejects wrong password', async () => {
    const hash = await hashPassword('correct-password');
    const valid = await verifyPassword('wrong-password', hash);
    expect(valid).toBe(false);
  });

  it('produces different hashes for same input', async () => {
    const h1 = await hashPassword('same');
    const h2 = await hashPassword('same');
    expect(h1).not.toBe(h2); // different salts
  });
});
