import { describe, expect, it } from 'vitest'
import { checkAntiSpam, issueRenderToken, verifyRenderToken } from './antispam.js'
import type { FormDef } from './types.js'

const SECRET = 'render-secret'

describe('issueRenderToken / verifyRenderToken', () => {
  it('verifies a token it issued and reports elapsed ageMs', async () => {
    const token = await issueRenderToken(SECRET, 1000)
    const v = await verifyRenderToken(SECRET, token, 4000)
    expect(v.ok).toBe(true)
    expect(v.ageMs).toBe(3000)
  })

  it('verify-before-trust: a tampered token is rejected and leaks NO age', async () => {
    const token = await issueRenderToken(SECRET, 1000)
    // tamper the embedded timestamp; signature no longer matches
    const tampered = token.replace(/^\d+/, '999999999999')
    const v = await verifyRenderToken(SECRET, tampered, 4000)
    expect(v.ok).toBe(false)
    expect(v.ageMs).toBeUndefined()
  })

  it('rejects a token signed under a different secret', async () => {
    const token = await issueRenderToken(SECRET, 1000)
    const v = await verifyRenderToken('other-secret', token, 4000)
    expect(v.ok).toBe(false)
    expect(v.ageMs).toBeUndefined()
  })

  it('rejects a structurally garbage token', async () => {
    const v = await verifyRenderToken(SECRET, 'no-dot-here', 4000)
    expect(v.ok).toBe(false)
  })

  it('fail-closed: issuing under an empty secret throws', async () => {
    await expect(issueRenderToken('', 1000)).rejects.toThrow()
  })

  it('fail-closed: verifying under an empty secret returns ok:false (never throws)', async () => {
    const token = await issueRenderToken(SECRET, 1000)
    const v = await verifyRenderToken('', token, 4000)
    expect(v.ok).toBe(false)
  })
})

const trapForm: FormDef = {
  id: 'contact',
  fields: [{ name: 'email', type: 'email', label: 'Email' }],
  antispam: { honeypot: 'website', minFillMs: 2000 },
}

async function tokenAt(now: number): Promise<string> {
  return issueRenderToken('s', now)
}

describe('checkAntiSpam', () => {
  it('ok when no antispam config', async () => {
    const v = await checkAntiSpam({ id: 'x', fields: [] }, {}, { now: 0 })
    expect(v.ok).toBe(true)
  })

  it('flags a filled honeypot', async () => {
    const v = await checkAntiSpam(trapForm, { website: 'bot' }, { now: 5000, secret: 's', renderToken: await tokenAt(0) })
    expect(v).toEqual({ ok: false, reason: 'honeypot' })
  })

  it('flags a too-fast submit (age < minFillMs)', async () => {
    const token = await tokenAt(1000)
    const v = await checkAntiSpam(trapForm, {}, { now: 1500, secret: 's', renderToken: token })
    expect(v).toEqual({ ok: false, reason: 'too-fast' })
  })

  it('passes a slow-enough submit (age >= minFillMs)', async () => {
    const token = await tokenAt(1000)
    const v = await checkAntiSpam(trapForm, {}, { now: 4000, secret: 's', renderToken: token })
    expect(v.ok).toBe(true)
  })

  it('flags a missing/forged render token as bad-token when a time-trap is set', async () => {
    const v = await checkAntiSpam(trapForm, {}, { now: 4000, secret: 's' })
    expect(v).toEqual({ ok: false, reason: 'bad-token' })
  })

  it('flags a failed challenge', async () => {
    const challengeForm: FormDef = {
      id: 'c',
      fields: [],
      antispam: { challenge: { verify: async () => false } },
    }
    const v = await checkAntiSpam(challengeForm, {}, { now: 0, challengeToken: 'x' })
    expect(v).toEqual({ ok: false, reason: 'challenge-failed' })
  })

  it('passes a satisfied challenge', async () => {
    const challengeForm: FormDef = {
      id: 'c',
      fields: [],
      antispam: { challenge: { verify: async () => true } },
    }
    const v = await checkAntiSpam(challengeForm, {}, { now: 0, challengeToken: 'x' })
    expect(v.ok).toBe(true)
  })
})
