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

describe('JWT RS256 support (Phase 4C)', () => {
  const SECRET = 'test-jwt-secret-at-least-32-chars-long!!';
  let rsaPrivateKey: string;
  let rsaPublicKey: string;

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

    // Generate RSA key pair for testing (extractable required for PEM export in jose v6)
    const { privateKey, publicKey } = await generateKeyPair('RS256', { extractable: true });
    const { exportPKCS8, exportSPKI } = await import('jose');
    rsaPrivateKey = await exportPKCS8(privateKey);
    rsaPublicKey = await exportSPKI(publicKey);
  });

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

  it('signs and verifies tokens with RS256', async () => {
    const jwt = createJwtUtils({
      issuer: 'test-rs256',
      algorithm: 'RS256',
      rsaPrivateKey,
      rsaPublicKey,
    });

    const token = await jwt.createAccessToken({
      sub: 'user-1',
      email: 'rs256@test.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('rs256@test.com');
    expect(payload!.type).toBe('access');
  });

  it('dual verify: RS256 verifier can fall back to HS256 tokens', async () => {
    // Create an HS256 token (simulates old tokens during migration)
    const hs256Jwt = createJwtUtils({ issuer: 'dual-test' });
    const hs256Token = await hs256Jwt.createAccessToken({
      sub: 'u-old',
      email: 'old@test.com',
      role: 'user',
    });

    // Verify with RS256-configured jwt (should fall back to HS256)
    const dualJwt = createJwtUtils({
      issuer: 'dual-test',
      algorithm: 'RS256',
      rsaPrivateKey,
      rsaPublicKey,
    });

    const payload = await dualJwt.verifyToken(hs256Token);
    expect(payload).not.toBeNull();
    expect(payload!.sub).toBe('u-old');
    expect(payload!.email).toBe('old@test.com');
  });

  it('RS256 token is NOT verified by HS256-only verifier with different issuer', async () => {
    const rs256Jwt = createJwtUtils({
      issuer: 'rs256-only',
      algorithm: 'RS256',
      rsaPrivateKey,
      rsaPublicKey,
    });

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

    // HS256-only verifier with different issuer should reject
    const hs256Jwt = createJwtUtils({ issuer: 'different-issuer' });
    const result = await hs256Jwt.verifyToken(token);
    expect(result).toBeNull();
  });

  it('RS256 refresh token works', async () => {
    const jwt = createJwtUtils({
      issuer: 'test-rs256',
      algorithm: 'RS256',
      rsaPrivateKey,
      rsaPublicKey,
    });

    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('throws when RS256 signing without private key', async () => {
    const jwt = createJwtUtils({
      issuer: 'test-no-key',
      algorithm: 'RS256',
      rsaPublicKey, // public only, no private
    });

    await expect(
      jwt.createAccessToken({ sub: 'u1', email: 'a@b.com', role: 'user' })
    ).rejects.toThrow('rsaPrivateKey is required');
  });

  it('HS256 still works as default (backward compatible)', async () => {
    const jwt = createJwtUtils({ issuer: 'hs256-compat' });

    const token = await jwt.createAccessToken({
      sub: 'u1',
      email: 'compat@test.com',
      role: 'user',
    });

    const payload = await jwt.verifyToken(token);
    expect(payload).not.toBeNull();
    expect(payload!.sub).toBe('u1');
    expect(payload!.email).toBe('compat@test.com');
  });
});
