import { describe, it, expect } from 'vitest'
import { writeFile, mkdtemp } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { peekPasetoClaims } from 'relay-auth/src/crypto/paseto.js'
import { createManageTokenMinter, ManageTokenError } from '../src/manage-token.js'
import { generatePkcs8Pem } from './helpers.js'

async function writeKey(): Promise<string> {
  const { pem } = await generatePkcs8Pem()
  const dir = await mkdtemp(join(tmpdir(), 'cp-key-'))
  const path = join(dir, 'capability-sign.key.pem')
  await writeFile(path, pem, 'utf8')
  return path
}

describe('createManageTokenMinter', () => {
  it('mints a manage token with the correct aud, sub, rights, and TTL', async () => {
    const keyPath = await writeKey()
    const minter = createManageTokenMinter({
      capabilitySignKeyPath: keyPath,
      baseDomain: 'terminal.yaojia.wang',
      operatorAccountId: 'acct-op-1',
    })

    const token = await minter.mint()
    expect(token.startsWith('v4.public.')).toBe(true)

    const claims = peekPasetoClaims(token) as Record<string, unknown>
    expect(claims.aud).toBe('terminal.yaojia.wang')
    expect(claims.sub).toBe('acct-op-1')
    expect(claims.rights).toEqual(['manage'])
    expect(typeof claims.host).toBe('string')
    expect((claims.exp as number) - (claims.iat as number)).toBe(60)
    // The additive DPoP proof-of-possession binding must be present.
    expect((claims.cnf as { jkt?: string })?.jkt).toMatch(/^[A-Za-z0-9_-]{43}$/)
  })

  it('mints a fresh token each call (distinct jti)', async () => {
    const keyPath = await writeKey()
    const minter = createManageTokenMinter({
      capabilitySignKeyPath: keyPath,
      baseDomain: 'd',
      operatorAccountId: 'a',
    })
    const a = peekPasetoClaims(await minter.mint()) as Record<string, unknown>
    const b = peekPasetoClaims(await minter.mint()) as Record<string, unknown>
    expect(a.jti).not.toBe(b.jti)
  })

  it('throws ManageTokenError when the key file is missing', async () => {
    const minter = createManageTokenMinter({
      capabilitySignKeyPath: '/nonexistent/does-not-exist.pem',
      baseDomain: 'd',
      operatorAccountId: 'a',
    })
    await expect(minter.mint()).rejects.toBeInstanceOf(ManageTokenError)
  })
})
