import { sql } from 'drizzle-orm'
import { beforeEach, describe, expect, it } from 'vitest'
import { createPgliteClient } from '../../db/src/postgres/pglite.js'
import { formsSchema } from './schema.js'
import { createDbSubmissionStore, recordSubmission, type SubmissionStore } from './store.js'
import { issueRenderToken } from './antispam.js'
import type { FormDef } from './types.js'

const CREATE_TABLE = sql`
  CREATE TABLE form_submissions (
    id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    form_id text NOT NULL,
    data jsonb NOT NULL,
    meta jsonb,
    spam boolean NOT NULL DEFAULT false,
    created_at timestamptz(3) NOT NULL DEFAULT NOW()
  )
`

let store: SubmissionStore

beforeEach(async () => {
  const db = createPgliteClient({ schema: formsSchema })
  await db.execute(CREATE_TABLE)
  store = createDbSubmissionStore(db)
})

describe('createDbSubmissionStore', () => {
  it('records and reads back a submission', async () => {
    const rec = await store.record({ formId: 'contact', data: { email: 'a@b.co' } })
    expect(rec.id).toBeTruthy()
    expect(rec.createdAt).toMatch(/\d{4}-\d{2}-\d{2}T/)
    const got = await store.get('contact', rec.id)
    expect(got?.data.email).toBe('a@b.co')
  })

  it('IDOR floor: get with the wrong formId returns null', async () => {
    const rec = await store.record({ formId: 'contact', data: { email: 'a@b.co' } })
    const cross = await store.get('other-form', rec.id)
    expect(cross).toBeNull()
  })

  it('list is scoped by formId and excludes spam by default', async () => {
    await store.record({ formId: 'contact', data: { x: '1' } })
    await store.record({ formId: 'contact', data: { x: '2' }, spam: true })
    await store.record({ formId: 'survey', data: { x: '3' } })
    const clean = await store.list('contact')
    expect(clean.items).toHaveLength(1)
    const withSpam = await store.list('contact', { includeSpam: true })
    expect(withSpam.items).toHaveLength(2)
  })

  it('delete is scoped by formId', async () => {
    const rec = await store.record({ formId: 'contact', data: { x: '1' } })
    await store.delete('other-form', rec.id) // wrong form — no-op
    expect(await store.get('contact', rec.id)).not.toBeNull()
    await store.delete('contact', rec.id)
    expect(await store.get('contact', rec.id)).toBeNull()
  })
})

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

describe('recordSubmission', () => {
  it('validation failure => Result error, NOTHING stored', async () => {
    const r = await recordSubmission({
      form: recForm,
      raw: { email: 'bad' },
      store,
      antispam: { now: 5000, secret: 's', renderToken: await issueRenderToken('s', 0) },
    })
    expect(r.ok).toBe(false)
    const list = await store.list('contact', { includeSpam: true })
    expect(list.items).toHaveLength(0)
  })

  it('clean submission => stored with spam=false', async () => {
    const r = await recordSubmission({
      form: recForm,
      raw: { email: 'a@b.co' },
      store,
      antispam: { now: 5000, secret: 's', renderToken: await issueRenderToken('s', 0), ip: '1.2.3.4' },
    })
    expect(r.ok).toBe(true)
    if (r.ok) {
      expect(r.value.spam).toBe(false)
      expect(r.value.data.email).toBe('a@b.co')
      expect(r.value.meta?.ip).toBe('1.2.3.4')
    }
  })

  it('SECURITY: oversized host-supplied meta is truncated to the 2000-char cap', async () => {
    // pins the security-guard META_MAX fix: ip/userAgent/ref are attacker-influenced and
    // persisted — an unbounded value is a storage-amplification DoS vector. Cap = 2000.
    const huge = 'u'.repeat(5000)
    const r = await recordSubmission({
      form: recForm,
      raw: { email: 'a@b.co' },
      store,
      antispam: { now: 5000, secret: 's', renderToken: await issueRenderToken('s', 0), userAgent: huge, ref: huge },
    })
    expect(r.ok).toBe(true)
    const got = r.ok ? await store.get('contact', r.value.id) : null
    expect(got?.meta?.userAgent?.length).toBe(2000)
    expect(got?.meta?.ref?.length).toBe(2000)
  })

  it('spam (honeypot) => QUARANTINED not lost: stored with spam=true', async () => {
    const r = await recordSubmission({
      form: recForm,
      raw: { email: 'a@b.co', website: 'bot-filled' },
      store,
      antispam: { now: 5000, secret: 's', renderToken: await issueRenderToken('s', 0) },
    })
    expect(r.ok).toBe(true)
    if (r.ok) expect(r.value.spam).toBe(true)
    // data-loss floor: it IS in the store, flagged
    const withSpam = await store.list('contact', { includeSpam: true })
    expect(withSpam.items).toHaveLength(1)
    const cleanOnly = await store.list('contact')
    expect(cleanOnly.items).toHaveLength(0)
  })
})
