import { describe, expect, it } from 'vitest';
import { MIN_FILL_MS, signFormToken, verifyFormToken } from './form-token';
import { HONEYPOT_FIELD, isHoneypotTripped, readPilotFormValues, validatePilotLead } from './pilot';
import type { PilotFormValues } from './pilot';

const valid: PilotFormValues = {
  venueName: '  The Test Tavern ',
  address: '1 Main St, Huntington, NY',
  contactName: 'Pat Tester',
  email: ' Pat@Example.COM ',
  phone: '',
  tvCount: '4',
  busiestNights: ['SAT', 'FRI', 'FRI'],
  currentEntertainment: '',
  message: '',
};

describe('validatePilotLead', () => {
  it('accepts a complete application and normalises it', () => {
    const result = validatePilotLead(valid);
    expect(result.ok).toBe(true);
    if (!result.ok) return;
    expect(result.data).toEqual({
      venueName: 'The Test Tavern',
      address: '1 Main St, Huntington, NY',
      contactName: 'Pat Tester',
      email: 'pat@example.com',
      phone: null,
      tvCount: 4,
      busiestNights: ['FRI', 'SAT'], // deduped, Mon→Sun order
      currentEntertainment: null,
      message: null,
    });
  });

  it('keeps optional fields when given', () => {
    const result = validatePilotLead({ ...valid, phone: '+1 (631) 555-0100', currentEntertainment: 'Trivia Tuesdays', message: 'Hi!' });
    expect(result.ok && result.data).toMatchObject({ phone: '+1 (631) 555-0100', currentEntertainment: 'Trivia Tuesdays', message: 'Hi!' });
  });

  it('returns one friendly message per invalid field', () => {
    const result = validatePilotLead({
      ...valid,
      venueName: ' ',
      email: 'not-an-email',
      tvCount: 'three',
      busiestNights: [],
      phone: '12',
    });
    expect(result.ok).toBe(false);
    if (result.ok) return;
    expect(result.fieldErrors).toEqual({
      venueName: 'Tell us the name of your venue.',
      email: 'Enter a valid email address, like you@yourbar.com.',
      tvCount: 'Use a whole number, like 3.',
      busiestNights: 'Pick at least one night.',
      phone: 'That phone number looks too short.',
    });
  });

  it('rejects out-of-range TV counts and unknown nights', () => {
    const zero = validatePilotLead({ ...valid, tvCount: '0' });
    expect(!zero.ok && zero.fieldErrors.tvCount).toBe('You’ll need at least one TV.');
    const many = validatePilotLead({ ...valid, tvCount: '500' });
    expect(!many.ok && many.fieldErrors.tvCount).toMatch(/more than 200/);
    const nights = validatePilotLead({ ...valid, busiestNights: ['FRIDAY'] });
    expect(!nights.ok && nights.fieldErrors.busiestNights).toBe('Pick nights from the list.');
  });

  it('enforces length limits', () => {
    const result = validatePilotLead({ ...valid, message: 'x'.repeat(2001) });
    expect(!result.ok && result.fieldErrors.message).toMatch(/under 2000 characters/);
  });
});

describe('readPilotFormValues / honeypot', () => {
  it('reads strings and multi-select values from FormData', () => {
    const form = new FormData();
    form.set('venueName', 'Bar');
    form.append('busiestNights', 'MON');
    form.append('busiestNights', 'TUE');
    const values = readPilotFormValues(form);
    expect(values.venueName).toBe('Bar');
    expect(values.email).toBe('');
    expect(values.busiestNights).toEqual(['MON', 'TUE']);
  });

  it('flags a filled honeypot only', () => {
    const form = new FormData();
    expect(isHoneypotTripped(form)).toBe(false);
    form.set(HONEYPOT_FIELD, '   ');
    expect(isHoneypotTripped(form)).toBe(false);
    form.set(HONEYPOT_FIELD, 'http://spam.example');
    expect(isHoneypotTripped(form)).toBe(true);
  });
});

describe('form tokens (minimum fill time)', () => {
  const secret = 'test-secret-for-form-tokens-0123456789';
  const issuedAt = 1_800_000_000_000;

  it('accepts a genuine token after the minimum fill time', () => {
    const token = signFormToken(issuedAt, secret, 'pilot');
    expect(verifyFormToken(token, issuedAt + MIN_FILL_MS, secret, 'pilot')).toMatchObject({ ok: true, ageMs: MIN_FILL_MS });
  });

  it('rejects submissions faster than 3 seconds', () => {
    const token = signFormToken(issuedAt, secret, 'pilot');
    expect(verifyFormToken(token, issuedAt + 2_999, secret, 'pilot')).toEqual({ ok: false, reason: 'too-fast' });
  });

  it('rejects forged, tampered, re-purposed, missing and stale tokens', () => {
    const token = signFormToken(issuedAt, secret, 'pilot');
    const later = issuedAt + 60_000;
    // Moving the timestamp back to fake an older form breaks the signature.
    const tampered = token.replace(String(issuedAt), String(issuedAt - 10_000));
    expect(verifyFormToken(tampered, later, secret, 'pilot')).toEqual({ ok: false, reason: 'bad-signature' });
    expect(verifyFormToken(token, later, 'another-secret', 'pilot')).toEqual({ ok: false, reason: 'bad-signature' });
    expect(verifyFormToken(token, later, secret, 'other-form')).toEqual({ ok: false, reason: 'bad-signature' });
    expect(verifyFormToken('', later, secret, 'pilot')).toEqual({ ok: false, reason: 'missing' });
    expect(verifyFormToken(null, later, secret, 'pilot')).toEqual({ ok: false, reason: 'missing' });
    expect(verifyFormToken('123.abc', later, secret, 'pilot')).toEqual({ ok: false, reason: 'malformed' });
    expect(verifyFormToken(token, issuedAt + 25 * 60 * 60 * 1000, secret, 'pilot')).toEqual({ ok: false, reason: 'expired' });
  });
});
