import { describe, expect, it, vi } from 'vitest'
import { base64url } from '@platform-modules/util/crypto'
import {
  computeWebPushIntermediates,
  createVapidJwt,
  createWebPushChannel,
  deriveEcdhSecret,
  encryptWebPushPayloadWithKeyMaterial,
  importRfc8291AsKeyPair,
  importUncompressedP256PublicKey,
  verifyVapidJwt,
} from './webpush.js'

// RFC 8291 §5 + Appendix A — byte-for-byte from https://www.rfc-editor.org/rfc/rfc8291.txt
const RFC = {
  plaintext: 'When I grow up, I want to be a watermelon',
  authSecret: 'BTBZMqHH6r4Tts7J_aSIgg',
  salt: 'DGv6ra1nlYgDCS1FRnbzlw',
  asPrivate: 'yfWPiYE-n46HLnH0KqZOF1fJJU3MYrct3AELtAQ-oRw',
  asPublic:
    'BP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIg' +
    'Dll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A8',
  uaPublic:
    'BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcx' +
    'aOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4',
  result:
    'DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27ml' +
    'mlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A_yl95bQpu6cVPT' +
    'pK4Mqgkf1CXztLVBSt2Ks3oZwbuwXPXLWyouBWLVWGNWQexSgSxsj_Qulcy4a-fN',
  ecdhSecret: 'kyrL1jIIOHEzg3sM2ZWRHDRB62YACZhhSlknJ672kSs',
  prkKey: 'Snr3JMxaHVDXHWJn5wdC52WjpCtd2EIEGBykDcZW32k',
  prk: '09_eUZGrsvxChDCGRCdkLiDXrReGOEVeSCdCcPBSJSc',
  cek: 'oIhVW04MRdy2XN9CiKLxTg',
  nonce: '4h_95klXJ5E_qnoN',
} as const

function b64urlDecode(input: string): Uint8Array {
  const padded = input.replace(/-/g, '+').replace(/_/g, '/')
  const pad = padded.length % 4 === 0 ? '' : '='.repeat(4 - (padded.length % 4))
  const binary = atob(padded + pad)
  const out = new Uint8Array(binary.length)
  for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i)
  return out
}

describe('RFC 8291 §5 known-answer test', () => {
  it('deterministic core matches §5 result and Appendix A intermediates', async () => {
    const asKeyPair = await importRfc8291AsKeyPair(RFC.asPrivate, RFC.asPublic)
    const salt = b64urlDecode(RFC.salt)
    const uaPublic = b64urlDecode(RFC.uaPublic)
    const asPublic = b64urlDecode(RFC.asPublic)

    const clientPublic = await importUncompressedP256PublicKey(RFC.uaPublic)
    const ecdhSecret = await deriveEcdhSecret(asKeyPair.privateKey!, clientPublic)
    expect(base64url(ecdhSecret)).toBe(RFC.ecdhSecret)

    const authSecret = b64urlDecode(RFC.authSecret)
    const stages = await computeWebPushIntermediates({
      ecdhSecret,
      authSecret,
      salt,
      uaPublic,
      asPublic,
    })
    expect(base64url(stages.prkKey)).toBe(RFC.prkKey)
    expect(base64url(stages.prk)).toBe(RFC.prk)
    expect(base64url(stages.cek)).toBe(RFC.cek)
    expect(base64url(stages.nonce)).toBe(RFC.nonce)

    const encrypted = await encryptWebPushPayloadWithKeyMaterial({
      payload: RFC.plaintext,
      clientP256dh: RFC.uaPublic,
      clientAuth: RFC.authSecret,
      salt,
      asKeyPair,
    })

    expect(base64url(encrypted.body)).toBe(RFC.result)
  })
})

describe('createVapidJwt', () => {
  it('produces an ES256 JWT that verifies offline', async () => {
    const vapid = {
      subject: 'mailto:admin@example.com',
      publicKey: RFC.asPublic,
      privateKey: RFC.asPrivate,
    }
    const aud = 'https://push.example.net'
    const exp = 2_000_000_000
    const jwt = await createVapidJwt(vapid, aud, exp)
    expect(await verifyVapidJwt(jwt, vapid.publicKey, aud)).toBe(true)
  })
})

describe('createWebPushChannel', () => {
  const vapid = {
    subject: 'mailto:admin@example.com',
    publicKey: RFC.asPublic,
    privateKey: RFC.asPrivate,
  }

  const subscription = {
    endpoint: 'https://push.example.net/push/sub-1',
    keys: { p256dh: RFC.uaPublic, auth: RFC.authSecret },
  }

  it('POSTs to faked endpoint with vapid + aes128gcm + TTL headers', async () => {
    let captured: { url: string; init: RequestInit } | undefined
    const fakeFetch = vi.fn(async (url: string | URL | Request, init?: RequestInit) => {
      captured = { url: String(url), init: init ?? {} }
      return new Response(null, { status: 201 })
    })

    const channel = createWebPushChannel({ vapid, ttl: 10, fetch: fakeFetch })
    const result = await channel.send({ body: 'hello' }, subscription)

    expect(result).toEqual({ ok: true })
    expect(fakeFetch).toHaveBeenCalledOnce()
    expect(captured?.url).toBe(subscription.endpoint)
    const headers = captured?.init.headers as Record<string, string>
    expect(headers['Content-Encoding']).toBe('aes128gcm')
    expect(headers.TTL).toBe('10')
    const authorization = headers.Authorization ?? ''
    expect(authorization).toMatch(/^vapid t=.+,\s*k=.+$/)

    const jwt = authorization.match(/^vapid t=([^,]+),/)?.[1]
    expect(jwt).toBeTruthy()
    expect(await verifyVapidJwt(jwt!, vapid.publicKey, 'https://push.example.net')).toBe(true)
  })

  it('410-gone → typed expired_subscription Result', async () => {
    const fakeFetch = vi.fn(async () => new Response(null, { status: 410 }))
    const channel = createWebPushChannel({ vapid, fetch: fakeFetch })
    const result = await channel.send({ body: 'hello' }, subscription)

    expect(result).toEqual({
      ok: false,
      error: {
        message: 'push subscription expired',
        code: 'expired_subscription',
        retryable: false,
      },
    })
  })
})
