import { exportJWK, importPKCS8, importSPKI, SignJWT, jwtVerify } from 'jose'
import type { JWTPayload } from 'jose'
import env from '#start/env'

const ALG = 'ES256'
const KID = '1'
const ACCESS_TOKEN_TTL = '15m'

class JwtService {
  #privateKey?: CryptoKey
  #publicKey?: CryptoKey

  async getPrivateKey(): Promise<CryptoKey> {
    if (!this.#privateKey) {
      const pem = Buffer.from(env.get('JWT_PRIVATE_KEY_BASE64').release(), 'base64')
        .toString('utf-8')
        .replace(/\r/g, '')
      this.#privateKey = await importPKCS8(pem, ALG)
    }
    return this.#privateKey
  }

  async getPublicKey(): Promise<CryptoKey> {
    if (!this.#publicKey) {
      const pem = Buffer.from(env.get('JWT_PUBLIC_KEY_BASE64'), 'base64')
        .toString('utf-8')
        .replace(/\r/g, '')
      this.#publicKey = await importSPKI(pem, ALG)
    }
    return this.#publicKey
  }

  async sign(payload: Record<string, unknown>): Promise<string> {
    const privateKey = await this.getPrivateKey()
    return new SignJWT(payload)
      .setProtectedHeader({ alg: ALG, kid: KID })
      .setIssuedAt()
      .setExpirationTime(ACCESS_TOKEN_TTL)
      .sign(privateKey)
  }

  async verify(token: string): Promise<JWTPayload> {
    const publicKey = await this.getPublicKey()
    const { payload } = await jwtVerify(token, publicKey)
    return payload
  }

  async getJwks(): Promise<{ keys: object[] }> {
    const publicKey = await this.getPublicKey()
    const jwk = await exportJWK(publicKey)
    return {
      keys: [{ ...jwk, use: 'sig', alg: ALG, kid: KID }],
    }
  }
}

export default new JwtService()
