import { describe, expect, it } from 'vitest'
import {
  DEFAULT_INTAKE_LIMITS, DEFAULT_INTAKE_POLICY, isDisposableEmail, riskDecision, riskScore, shouldEnterProtection, sniffFileType,
  submissionDecision, trustLevel, validateIntakeFiles,
} from '@/lib/domain/intake-security'

/** Public intake security (2026-09-22): easy for a dispatcher, expensive for a bot. */
const L = DEFAULT_INTAKE_LIMITS, P = DEFAULT_INTAKE_POLICY
const base = { isAdmin: false, blocked: false, policy: P, limits: L, counts: { userToday: 0, ipLastHour: 0, pageLastHour: 0 }, protectionActive: false, invited: false }

describe('trust levels', () => {
  it('anonymous 0 · unverified 1 · verified 2 · company 3 · invited/trusted/admin 4', () => {
    expect(trustLevel({ signedIn: false, emailVerified: false, companyVerified: false, invited: false, trusted: false, isAdmin: false })).toBe(0)
    expect(trustLevel({ signedIn: true, emailVerified: false, companyVerified: false, invited: false, trusted: false, isAdmin: false })).toBe(1)
    expect(trustLevel({ signedIn: true, emailVerified: true, companyVerified: false, invited: false, trusted: false, isAdmin: false })).toBe(2)
    expect(trustLevel({ signedIn: true, emailVerified: true, companyVerified: true, invited: false, trusted: false, isAdmin: false })).toBe(3)
    expect(trustLevel({ signedIn: true, emailVerified: false, companyVerified: false, invited: true, trusted: false, isAdmin: false })).toBe(4)
  })
})

describe('submission decision (§36–§38)', () => {
  it('anonymous can never complete; unverified must verify; verified goes', () => {
    expect(submissionDecision({ ...base, level: 0 })).toEqual({ allowed: false, reason: 'auth_required' })
    expect(submissionDecision({ ...base, level: 1 })).toEqual({ allowed: false, reason: 'email_verification_required' })
    expect(submissionDecision({ ...base, level: 2 })).toEqual({ allowed: true })
  })
  it('quotas by level and velocity by IP / page', () => {
    expect(submissionDecision({ ...base, level: 2, counts: { userToday: 10, ipLastHour: 0, pageLastHour: 0 } })).toEqual({ allowed: false, reason: 'daily_quota' })
    expect(submissionDecision({ ...base, level: 3, counts: { userToday: 10, ipLastHour: 0, pageLastHour: 0 } })).toEqual({ allowed: true })
    expect(submissionDecision({ ...base, level: 2, counts: { userToday: 0, ipLastHour: 20, pageLastHour: 0 } })).toEqual({ allowed: false, reason: 'ip_velocity' })
    expect(submissionDecision({ ...base, level: 2, counts: { userToday: 0, ipLastHour: 0, pageLastHour: 200 } })).toEqual({ allowed: false, reason: 'page_velocity' })
    expect(submissionDecision({ ...base, level: 2, policy: { ...P, submissions_per_hour_limit: 5 }, counts: { userToday: 0, ipLastHour: 0, pageLastHour: 5 } })).toEqual({ allowed: false, reason: 'page_velocity' })
  })
  it('broker policy: disabled, invited only, verified company only; protection mode admits invited only', () => {
    expect(submissionDecision({ ...base, level: 2, policy: { ...P, intake_enabled: false } })).toEqual({ allowed: false, reason: 'intake_disabled' })
    expect(submissionDecision({ ...base, level: 2, policy: { ...P, who_can_submit: 'invited_only' } })).toEqual({ allowed: false, reason: 'not_invited' })
    expect(submissionDecision({ ...base, level: 4, invited: true, policy: { ...P, who_can_submit: 'invited_only' } })).toEqual({ allowed: true })
    expect(submissionDecision({ ...base, level: 2, policy: { ...P, who_can_submit: 'verified_company' } })).toEqual({ allowed: false, reason: 'company_verification_required' })
    expect(submissionDecision({ ...base, level: 2, protectionActive: true })).toEqual({ allowed: false, reason: 'protection_mode' })
    expect(submissionDecision({ ...base, level: 4, invited: true, protectionActive: true })).toEqual({ allowed: true })
  })
  it('blocked always loses; admin skips the quotas but not a disabled page', () => {
    expect(submissionDecision({ ...base, level: 4, blocked: true })).toEqual({ allowed: false, reason: 'blocked' })
    expect(submissionDecision({ ...base, level: 4, isAdmin: true, counts: { userToday: 999, ipLastHour: 999, pageLastHour: 999 } })).toEqual({ allowed: true })
    expect(submissionDecision({ ...base, level: 4, isAdmin: true, policy: { ...P, intake_enabled: false } })).toEqual({ allowed: false, reason: 'intake_disabled' })
  })
  it('protection mode switches on at the page threshold', () => {
    expect(shouldEnterProtection(499, L)).toBe(false)
    expect(shouldEnterProtection(500, L)).toBe(true)
  })
})

describe('files (§20): content, not extension', () => {
  const pdf = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]), png = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d]), jpg = new Uint8Array([0xff, 0xd8, 0xff, 0xe0]), exe = new Uint8Array([0x4d, 0x5a, 0x90, 0x00])
  it('sniffs PDF / JPG / PNG and rejects anything else', () => {
    expect(sniffFileType(pdf)).toBe('application/pdf')
    expect(sniffFileType(jpg)).toBe('image/jpeg')
    expect(sniffFileType(png)).toBe('image/png')
    expect(sniffFileType(exe)).toBeNull()
    const r = validateIntakeFiles([{ name: 'permit.pdf', size: 10, head: exe }], L, P)
    expect(r.ok).toBe(false)
    if (!r.ok) expect(r.error).toContain('is not a PDF, JPG or PNG')
  })
  it('counts, per-file and total size, with broker overrides', () => {
    const f = (n: number) => Array.from({ length: n }, (_, i) => ({ name: `f${i}.pdf`, size: 1024, head: pdf }))
    expect(validateIntakeFiles(f(11), L, P).ok).toBe(false)
    expect(validateIntakeFiles(f(10), L, P).ok).toBe(true)
    expect(validateIntakeFiles(f(3), L, { ...P, max_files: 2 }).ok).toBe(false)
    expect(validateIntakeFiles([{ name: 'big.pdf', size: 21 * 1024 * 1024, head: pdf }], L, P).ok).toBe(false)
    expect(validateIntakeFiles([{ name: 'a.pdf', size: 40 * 1024 * 1024, head: pdf }, { name: 'b.pdf', size: 40 * 1024 * 1024, head: pdf }], { ...L, max_file_mb: 50 }, P).ok).toBe(false)
  })
})

describe('risk (§26)', () => {
  it('honeypot or a failed bot check blocks; a verified company with clean signals is low risk', () => {
    const clean = { level: 3 as const, accountAgeHours: 200, userToday: 1, ipLastHour: 1, ipAccountsLastDay: 0, disposableEmail: false, duplicateFiles: 0, botCheckFailed: false, recentVerificationFailures: 0, honeypotFilled: false }
    expect(riskDecision(riskScore(clean).score, L)).toBe('allow')
    expect(riskDecision(riskScore({ ...clean, honeypotFilled: true }).score, L)).toBe('block')
    expect(riskDecision(riskScore({ ...clean, level: 1, botCheckFailed: true }).score, L)).toBe('block')
    expect(riskDecision(riskScore({ ...clean, level: 2, disposableEmail: true, ipAccountsLastDay: 3, duplicateFiles: 1 }).score, L)).toBe('review')
    expect(isDisposableEmail('x@mailinator.com')).toBe(true)
  })
})
