import { describe, test, expect } from 'vitest';
import { buildReportPayload, MAX_PAYLOAD_BYTES } from './report-payload';

const base = {
  category: 'bug' as const,
  description: 'The HTML block colours do nothing',
  includeCanvas: true,
  siteId: 42,
  siteDomain: 'example.com',
  pageId: 'home',
  pageSlug: 'index',
  editorVersion: 'abc1234-2026-08-08',
  userAgent: 'Mozilla/5.0 test',
  viewport: '1920x1080',
  deviceMode: 'desktop',
  selectedType: 'HTML',
  consoleErrors: [{ ts: 1, message: 'oops' }],
  canvasState: '{"ROOT":{}}',
};

describe('buildReportPayload', () => {
  test('carries every context field through', () => {
    const p = buildReportPayload(base);
    expect(p.category).toBe('bug');
    expect(p.description).toBe('The HTML block colours do nothing');
    expect(p.site_id).toBe(42);
    expect(p.site_domain).toBe('example.com');
    expect(p.page_slug).toBe('index');
    expect(p.editor_version).toBe('abc1234-2026-08-08');
    expect(p.device_mode).toBe('desktop');
    expect(p.selected_type).toBe('HTML');
    expect(p.console_errors).toEqual([{ ts: 1, message: 'oops' }]);
    expect(p.canvas_state).toBe('{"ROOT":{}}');
    expect(p.canvas_state_omitted).toBeUndefined();
  });

  test('includeCanvas=false drops the canvas and records why', () => {
    const p = buildReportPayload({ ...base, includeCanvas: false });
    expect(p.canvas_state).toBeNull();
    expect(p.canvas_state_omitted).toBe('opt-out');
    expect(p.console_errors).toHaveLength(1);
    expect(p.site_domain).toBe('example.com');
  });

  test('an oversized canvas is dropped rather than truncated', () => {
    const huge = 'x'.repeat(MAX_PAYLOAD_BYTES + 1000);
    const p = buildReportPayload({ ...base, canvasState: huge });
    expect(p.canvas_state).toBeNull();
    expect(p.canvas_state_omitted).toBe('size');
  });

  test('the resulting payload always fits under the cap', () => {
    const huge = 'x'.repeat(MAX_PAYLOAD_BYTES * 2);
    const p = buildReportPayload({ ...base, canvasState: huge });
    expect(new Blob([JSON.stringify(p)]).size).toBeLessThanOrEqual(MAX_PAYLOAD_BYTES);
  });

  test('description is trimmed', () => {
    expect(buildReportPayload({ ...base, description: '  spaced  ' }).description).toBe('spaced');
  });

  test('a null canvasState is reported as opt-out-free but still null', () => {
    const p = buildReportPayload({ ...base, canvasState: null });
    expect(p.canvas_state).toBeNull();
    expect(p.canvas_state_omitted).toBeUndefined();
  });
});

describe('description size bounding', () => {
  const hugeDescription = 'y'.repeat(600 * 1024);

  test('oversized description does not blow the cap with canvas included', () => {
    const p = buildReportPayload({ ...base, description: hugeDescription });
    expect(new Blob([JSON.stringify(p)]).size).toBeLessThanOrEqual(MAX_PAYLOAD_BYTES);
  });

  test('oversized description does not blow the cap when opted out', () => {
    const p = buildReportPayload({ ...base, description: hugeDescription, includeCanvas: false });
    expect(new Blob([JSON.stringify(p)]).size).toBeLessThanOrEqual(MAX_PAYLOAD_BYTES);
    expect(p.canvas_state).toBeNull();
    expect(p.canvas_state_omitted).toBe('opt-out');
  });

  test('oversized description does not blow the cap with no canvas state', () => {
    const p = buildReportPayload({ ...base, description: hugeDescription, canvasState: null });
    expect(new Blob([JSON.stringify(p)]).size).toBeLessThanOrEqual(MAX_PAYLOAD_BYTES);
    expect(p.canvas_state).toBeNull();
    expect(p.canvas_state_omitted).toBeUndefined();
  });

  test('description is truncated to 5000 characters', () => {
    const p = buildReportPayload({ ...base, description: hugeDescription });
    expect(p.description.length).toBeLessThanOrEqual(5000);
  });
});

describe('honest size markers', () => {
  test('the size marker never appears on a payload that is still oversized after dropping canvas_state', () => {
    // consoleErrors is normally bounded upstream (console-buffer.ts caps it at
    // 20 entries x 500 chars), but this function must not trust that -- it's
    // just an array of the exported type as far as the signature is concerned.
    const massiveErrors = Array.from({ length: 5000 }, (_, i) => ({ ts: i, message: 'x'.repeat(200) }));
    expect(() => buildReportPayload({ ...base, consoleErrors: massiveErrors })).toThrow();
  });
});
