import test from 'node:test';
import assert from 'node:assert/strict';
import { chromium } from 'playwright';
import { calendarDays, workingDays, isDate } from '../../src/tools/shared/dates';
import { plannerExamples, validatePlan, calculatePlan, exportPlanCsv } from '../../src/tools/planner/core';
import { validateProcess, processExamples, compileGuided, validateSource } from '../../src/tools/mapper/core';
import { parseJson } from '../../src/tools/shared/validation';

test('independent review: century boundaries and large decimal task amounts use calendar and integer expectations', () => {
  assert.equal(isDate('1900-02-29'), false); assert.equal(isDate('2000-02-29'), true); assert.equal(isDate('2100-02-29'), false);
  assert.equal(calendarDays('1900-02-28', '1900-03-01'), 2); assert.equal(calendarDays('2000-02-28', '2000-03-01'), 3);
  assert.equal(workingDays('2026-09-12', '2026-09-13'), 0);
  const plan = structuredClone(plannerExamples['customer-portal']);
  plan.tasks = [{ ...plan.tasks[0], hours: '99999.9999', rate: '999999.9999' }]; plan.contingencyPercent = '100';
  const product = 999999999n * 9999999999n;
  const expectedCents = (product + 500000n) / 1000000n;
  const totalCents = expectedCents * 2n;
  assert.equal(calculatePlan(plan).totalCost, `${totalCents / 100n}.${String(totalCents % 100n).padStart(2, '0')}`);
  for (const date of ['2199-12-27', '2199-12-28', '2199-12-29', '2199-12-30', '2199-12-31']) {
    const edge = structuredClone(plannerExamples['customer-portal']); edge.tasks = edge.tasks.slice(0, 2);
    edge.tasks.forEach(task => { task.start = date; task.end = date; });
    assert.doesNotThrow(() => validatePlan(edge));
  }
});

test('independent review: control-prefixed spreadsheet formulas remain inert and imported engine configuration is rejected', () => {
  for (const prefix of ['\t', '\r', '\n', '\uFEFF', '  \t']) {
    const plan = structuredClone(plannerExamples['customer-portal']); plan.tasks[0].title = `${prefix}=1+1`;
    assert.ok(exportPlanCsv(plan).includes(`"'${prefix}=1+1"`));
  }
  const source = structuredClone(processExamples.onboarding);
  for (const extra of [{ themeCSS: '@import url(https://example.invalid/leak)' }, { securityLevel: 'loose' }, { plugins: ['remote'] }]) assert.equal(validateProcess({ ...source, ...extra }).ok, false);
  for (const property of ['__proto__', 'constructor', 'prototype']) {
    const payload = JSON.stringify({ harmless: 'value' }).replace('"harmless"', JSON.stringify(property));
    assert.throws(() => parseJson(payload));
  }
});

test('independent review: guided delimiters stay text and source mode forbids resources and executable directives', () => {
  const process = structuredClone(processExamples.onboarding);
  process.graph.nodes[0].label = '"]; click enquiry "https://example.invalid"; <svg onload="alert(1)">';
  const compiled = compileGuided(process.graph);
  assert.equal(compiled.includes('<svg'), false); assert.equal(compiled.includes('onload="'), false);
  assert.equal(compiled.split('\n').length, 1 + process.graph.nodes.length + process.graph.edges.length);
  for (const source of [
    'flowchart TB\nA["Safe"]\nclick A "https://example.invalid"',
    '%%{init: {"securityLevel":"loose"}}%%\nflowchart TB\nA["Safe"]',
    'flowchart TB\nA@{img:"https://example.invalid/x"}',
    'flowchart TB\nA["Safe"]\nstyle A fill:url(https://example.invalid/x)',
    'sequenceDiagram\nparticipant A\nA->>A: <script>alert(1)</script>',
  ]) assert.ok(validateSource(source).length > 0);
});

const base = process.env.ESSENTIAL_BROWSER_BASE;
test('independent review browser: restore never opts into storage and only an editable JSON save clears unsaved-work protection', { skip: !base, timeout: 120_000 }, async () => {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage();
    const shouldWarn = () => page.evaluate(() => { const event = new Event('beforeunload', { cancelable: true }); window.dispatchEvent(event); return event.defaultPrevented; });
    const download = async (selector: string) => { const event = page.waitForEvent('download'); await page.locator(selector).click(); await event; };
    await page.goto(`${base}/tools/project-planner/`);
    await page.locator('[data-project-field="title"]').waitFor();
    await page.evaluate(value => localStorage.setItem('essential-tools:project-planner:v1', JSON.stringify(value)), plannerExamples['internal-application']);
    assert.equal(await page.locator('[data-remember]').isChecked(), false);
    page.once('dialog', dialog => dialog.accept()); await page.locator('[data-action="restore"]').click();
    assert.equal(await page.locator('[data-remember]').isChecked(), false, 'Restoring must not opt the user into further device saving.');
    await page.locator('[data-project-field="title"]').fill('Changed after restoring'); await page.locator('[data-project-field="title"]').press('Tab');
    assert.equal(await page.evaluate(() => JSON.parse(localStorage.getItem('essential-tools:project-planner:v1')!).title), plannerExamples['internal-application'].title);
    await page.locator('[data-remember]').check();
    await page.locator('[data-project-field="title"]').fill(''); await page.locator('[data-project-field="title"]').press('Tab');
    assert.equal(await shouldWarn(), true, 'Remembering cannot protect an invalid plan that was not saved.');
    await page.locator('[data-project-field="title"]').fill('Valid remembered plan'); await page.locator('[data-project-field="title"]').press('Tab');
    assert.equal(await shouldWarn(), false);
    await page.evaluate(() => { const original = Storage.prototype.setItem; Storage.prototype.setItem = function(key, value) { if (key === 'essential-tools:project-planner:v1') throw new DOMException('Test quota failure', 'QuotaExceededError'); original.call(this, key, value); }; });
    await page.locator('[data-project-field="title"]').fill('Valid but storage rejected'); await page.locator('[data-project-field="title"]').press('Tab');
    assert.equal(await shouldWarn(), true, 'A rejected storage write must keep navigation protection.');
    await download('[data-action="json"]');
    await page.goto(`${base}/tools/document-builder/`);
    await page.locator('[data-field="business.name"]').fill('Unsaved fictional business');
    assert.equal(await shouldWarn(), true, 'Unsaved document changes need navigation protection.');
    await download('[data-action="json"]'); assert.equal(await shouldWarn(), false);
    await page.locator('[data-field="business.name"]').fill('Changed again');
    await page.waitForFunction(() => !document.querySelector<HTMLButtonElement>('.et-document [data-action="pdf"]')?.disabled);
    await download('[data-action="pdf"]'); assert.equal(await shouldWarn(), true, 'PDF cannot restore the editable document data.');
    await download('[data-action="json"]');
    await page.goto(`${base}/tools/process-mapper/`);
    await page.locator('[data-node-field="description"]').fill('Metadata that the diagram image does not contain.');
    await page.waitForFunction(() => !document.querySelector<HTMLButtonElement>('[data-export="svg"]')?.disabled);
    await download('[data-export="svg"]'); assert.equal(await shouldWarn(), true, 'SVG cannot restore guided team/description metadata.');
    await download('[data-export="json"]'); assert.equal(await shouldWarn(), false);
    await page.locator('[data-remember]').check();
    await page.locator('[data-field="title"]').fill('');
    assert.equal(await shouldWarn(), true, 'Remembering cannot protect an invalid map that was not saved.');
    await page.locator('[data-field="title"]').fill('Valid remembered process');
    assert.equal(await shouldWarn(), false);
    await page.evaluate(() => { const original = Storage.prototype.setItem; Storage.prototype.setItem = function(key, value) { if (key === 'essential-tools:process-mapper:v1') throw new DOMException('Test quota failure', 'QuotaExceededError'); original.call(this, key, value); }; });
    await page.locator('[data-field="title"]').fill('Valid process with rejected storage');
    assert.equal(await shouldWarn(), true, 'A rejected map storage write must keep navigation protection.');
  } finally { await browser.close(); }
});

test('independent review browser: edits during delayed PDF generation are preserved and an outdated PDF is not downloaded', { skip: !base, timeout: 120_000 }, async () => {
  const browser = await chromium.launch();
  let releaseFont = () => {};
  try {
    const page = await browser.newPage();
    const gate = new Promise<void>(resolve => { releaseFont = resolve; });
    await page.route('**/tools-assets/fonts/NotoSans-Regular.ttf', async route => { await gate; await route.continue(); });
    await page.goto(`${base}/tools/project-planner/`);
    const fontRequested = page.waitForRequest(request => request.url().endsWith('/tools-assets/fonts/NotoSans-Regular.ttf'));
    let downloads = 0; page.on('download', () => { downloads++; });
    await page.locator('[data-action="pdf"]').click(); await fontRequested;
    await page.locator('[data-project-field="title"]').fill('Keep these edits made during PDF creation');
    await page.locator('[data-project-field="title"]').press('Tab');
    releaseFont();
    await page.locator('[data-planner-status]').filter({ hasText: 'Your plan changed while the PDF was being prepared' }).waitFor();
    assert.equal(downloads, 0);
    assert.equal(await page.locator('[data-project-field="title"]').inputValue(), 'Keep these edits made during PDF creation');
    const file = page.waitForEvent('download'); await page.locator('[data-action="pdf"]').click(); await file;
    assert.equal(downloads, 1, 'A fresh export should still work after stale output is suppressed.');
  } finally { releaseFont(); await browser.close(); }
});
