import { describe, test, expect } from 'vitest';
import { ContactForm } from './ContactForm';

const toHtml = (ContactForm as any).toHtml;

describe('ContactForm.toHtml relay wiring', () => {
  test('with recipientEmail: emits marker, placeholder action, honeypot', () => {
    const { html } = toHtml({ recipientEmail: 'a@b.com', thankYouUrl: '/thx', fields: [] }, '');
    expect(html).toMatch(/<!--WHP-FORM id="F_[0-9a-z]+" recipient="a@b.com" thankyou="\/thx"-->/);
    expect(html).toMatch(/action="__WHP_FORM_ACTION__F_[0-9a-z]+__"/);
    expect(html).toContain('method="POST"');
    expect(html).toContain('name="_gotcha"');
    // marker id and action id match
    const mid = html.match(/id="(F_[0-9a-z]+)"/)![1];
    expect(html).toContain(`__WHP_FORM_ACTION__${mid}__`);
  });

  test('without recipientEmail: no marker, falls back to formAction', () => {
    const { html } = toHtml({ formAction: '/legacy', fields: [] }, '');
    expect(html).not.toContain('WHP-FORM');
    expect(html).toContain('action="/legacy"');
    expect(html).not.toContain('_gotcha');
    // Backward-compat: ensure non-relay output is byte-identical (no extra blank lines from honeypot)
    expect(html).not.toMatch(/<form[^>]*>\n\s*\n/);
  });

  test('without recipientEmail + real fields: byte-clean legacy output (realistic case)', () => {
    // The empty-fields case is NOT byte-identical to the old code (the old
    // template emitted a stray whitespace line when fields was empty; the new
    // ternary drops it). Real forms always have fields, so pin THAT scenario:
    // no marker, no honeypot, and no whitespace-only line between <form> and
    // the first field.
    const fields = [{ type: 'text', label: 'Name', name: 'name', placeholder: 'Your name', required: true }];
    const { html } = toHtml({ formAction: '/legacy', fields }, '');
    expect(html).not.toContain('WHP-FORM');
    expect(html).not.toContain('_gotcha');
    expect(html).toContain('action="/legacy"');
    expect(html).not.toMatch(/<form[^>]*>\n\s*\n/);
    // First field renders directly after the form tag (no stray blank line).
    expect(html).toMatch(/<form[^>]*>\n\s*<div/);
    expect(html).toContain('Name');
  });
});

describe('ContactForm.toHtml successMessage', () => {
  // The published form-sender relay (form-sender/app/submit.php) delivers
  // success via a full-page 303 redirect to thankYouUrl or a hosted
  // thanks.php page -- there is no in-page JS to reveal an inline success
  // element. So successMessage is emitted as a forward-compatible data
  // attribute for a future AJAX/JS submission mode, not a live DOM element.
  test('with successMessage set: emits it as an escaped data attribute on the form', () => {
    const { html } = toHtml({ successMessage: "We'll be in touch!", fields: [] }, '');
    expect(html).toContain('data-whp-success-message="We&#39;ll be in touch!"');
  });

  test('without successMessage: no data attribute emitted', () => {
    const { html } = toHtml({ fields: [] }, '');
    expect(html).not.toContain('data-whp-success-message');
  });

  test('escapes attribute-breakout attempts in successMessage', () => {
    const { html } = toHtml({ successMessage: 'x" onerror="alert(1)', fields: [] }, '');
    expect(html).not.toContain('onerror="alert(1)"');
  });
});

describe('ContactForm.toHtml relay marker deterministic + unique via node id (no Math.random)', () => {
  test('same node id -> identical marker+placeholder ids across two calls', () => {
    const { html: html1 } = toHtml({ recipientEmail: 'a@b.com', thankYouUrl: '/thx', fields: [] }, '', 'node-cf1');
    const { html: html2 } = toHtml({ recipientEmail: 'a@b.com', thankYouUrl: '/thx', fields: [] }, '', 'node-cf1');
    expect(html1).toBe(html2);
  });

  test('marker id always equals the placeholder id it pairs with', () => {
    const { html } = toHtml({ recipientEmail: 'a@b.com', thankYouUrl: '/thx', fields: [] }, '', 'node-cf1');
    const mid = html.match(/<!--WHP-FORM id="([^"]+)"/)![1];
    expect(html).toContain(`action="__WHP_FORM_ACTION__${mid}__"`);
  });

  test('two different node ids -> different fids', () => {
    const { html: html1 } = toHtml({ recipientEmail: 'a@b.com', thankYouUrl: '/thx', fields: [] }, '', 'node-cf1');
    const { html: html2 } = toHtml({ recipientEmail: 'a@b.com', thankYouUrl: '/thx', fields: [] }, '', 'node-cf2');
    const mid1 = html1.match(/<!--WHP-FORM id="([^"]+)"/)![1];
    const mid2 = html2.match(/<!--WHP-FORM id="([^"]+)"/)![1];
    expect(mid1).not.toBe(mid2);
  });
});

describe('ContactForm.toHtml accessibility (F2.1)', () => {
  const fields = [
    { type: 'text' as const, label: 'Name', name: 'name', placeholder: 'Your name', required: true },
    { type: 'email' as const, label: 'Email', name: 'email', placeholder: 'you@example.com', required: true },
  ];

  test('each field label for= matches its control id=, and ids are unique', () => {
    const { html } = toHtml({ fields }, '');
    const labelIds = [...html.matchAll(/<label for="([^"]+)"/g)].map((m) => m[1]);
    const controlIds = [...html.matchAll(/<(?:input|textarea|select) id="([^"]+)"/g)].map((m) => m[1]);
    expect(labelIds.length).toBe(2);
    expect(controlIds.length).toBe(2);
    expect(labelIds).toEqual(controlIds);
    expect(new Set(controlIds).size).toBe(2);
  });

  test('ids are deterministic across repeated calls with the same fields', () => {
    const { html: html1 } = toHtml({ fields }, '');
    const { html: html2 } = toHtml({ fields }, '');
    const ids1 = [...html1.matchAll(/<input id="([^"]+)"/g)].map((m) => m[1]);
    const ids2 = [...html2.matchAll(/<input id="([^"]+)"/g)].map((m) => m[1]);
    expect(ids1).toEqual(ids2);
  });
});

describe('ContactForm.toHtml field type attribute sanitization', () => {
  test('malicious field.type cannot break out of the input attribute; falls back to type="text"', () => {
    const fields = [{ type: 'text"><img src=x onerror=alert(1)>' as any, label: 'Name', name: 'name', placeholder: 'Your name', required: false }];
    const { html } = toHtml({ fields }, '');
    expect(html).not.toContain('<img');
    expect(html).not.toContain('onerror=');
    expect(html).toContain('type="text"');
  });

  test('legitimate email field type still passes through unchanged', () => {
    const fields = [{ type: 'email' as const, label: 'Email', name: 'email', placeholder: 'you@example.com', required: false }];
    const { html } = toHtml({ fields }, '');
    expect(html).toContain('type="email"');
  });
});
