import assert from 'node:assert/strict'
import { writeFile } from 'node:fs/promises'
import { performance } from 'node:perf_hooks'
import {
  validateBooleanCutInput,
  validateBooleanUnionInput,
  validateBoxInput,
  validateChamferInput,
  validateConeInput,
  validateCylinderInput,
  validateFilletInput,
  validateGeometryFileImport,
  validateGrooveInput,
  validateLoftInput,
  validateMirrorInput,
  validatePadInput,
  validatePipeInput,
  validatePlacementInput,
  validatePocketInput,
  validateRevolutionInput,
  validateTorusInput,
} from '../src/facade/geometryRuntime'
import type { PlanarProfile, ShapeHandle } from '../src/facade/types'

type GeometryFuzzCase = { name: string; expected: 'accept' | 'reject'; invoke: () => void }

let state = 0x9e3779b9
const random = () => {
  state ^= state << 13
  state ^= state >>> 17
  state ^= state << 5
  return state >>> 0
}
const positive = () => (random() % 100_000 + 1) / 1000
const context = () => ({ documentId: `geometry-fuzz-${random() % 7}`, documentVersion: random() % 100 })
const shape = (documentId: string, documentVersion: number, suffix: string): ShapeHandle => ({ id: `shape-${suffix}`, kernel: 'bitbybit-occt', kind: 'solid', documentId, documentVersion })
const square = (size = positive()): PlanarProfile => ({ outer: [[0, 0, 0], [size, 0, 0], [size, size, 0], [0, size, 0]] })
const elevatedSquare = (): PlanarProfile => {
  const profile = square()
  const z = positive()
  return { outer: profile.outer.map(([x, y]): [number, number, number] => [x, y, z]) }
}
const invalidScalar = () => [0, -positive(), Number.NaN, Number.POSITIVE_INFINITY][random() % 4]

const createCase = (index: number): GeometryFuzzCase => {
  const owner = context()
  const base = shape(owner.documentId, Math.max(0, owner.documentVersion - 1), 'base')
  const tool = shape(owner.documentId, owner.documentVersion, 'tool')
  switch (index % 30) {
    case 0: return { name: 'valid-box', expected: 'accept', invoke: () => validateBoxInput({ ...owner, width: positive(), length: positive(), height: positive(), center: [positive(), -positive(), 0] }) }
    case 1: return { name: 'invalid-box-scalar', expected: 'reject', invoke: () => validateBoxInput({ ...owner, width: invalidScalar(), length: positive(), height: positive() }) }
    case 2: return { name: 'valid-cylinder', expected: 'accept', invoke: () => validateCylinderInput({ ...owner, radius: positive(), height: positive(), direction: [positive(), positive(), positive()], angle: positive() % 360 || 360 }) }
    case 3: return { name: 'zero-cylinder-axis', expected: 'reject', invoke: () => validateCylinderInput({ ...owner, radius: positive(), height: positive(), direction: [0, 0, 0] }) }
    case 4: return { name: 'zero-cone-radii', expected: 'reject', invoke: () => validateConeInput({ ...owner, radius1: 0, radius2: 0, height: positive() }) }
    case 5: return { name: 'valid-placement', expected: 'accept', invoke: () => validatePlacementInput({ ...owner, shape: base, placement: { translation: [positive(), 0, -positive()], rotationAxis: [0, 0, 1], rotationAngle: random() % 361 } }) }
    case 6: return { name: 'foreign-placement-shape', expected: 'reject', invoke: () => validatePlacementInput({ ...owner, shape: { ...base, documentId: 'foreign-document' }, placement: { translation: [0, 0, 0], rotationAxis: [0, 1, 0], rotationAngle: 0 } }) }
    case 7: return { name: 'valid-union', expected: 'accept', invoke: () => validateBooleanUnionInput({ ...owner, shapes: [base, tool] }) }
    case 8: return { name: 'short-union', expected: 'reject', invoke: () => validateBooleanUnionInput({ ...owner, shapes: [base] }) }
    case 9: return { name: 'empty-cut-tools', expected: 'reject', invoke: () => validateBooleanCutInput({ ...owner, base, tools: [] }) }
    case 10: return { name: 'fractional-fillet-index', expected: 'reject', invoke: () => validateFilletInput({ ...owner, base, radius: positive(), indexes: [random() % 10 + 0.5] }) }
    case 11: return { name: 'valid-chamfer', expected: 'accept', invoke: () => validateChamferInput({ ...owner, base, distance: positive(), indexes: [random() % 20] }) }
    case 12: return { name: 'valid-pad', expected: 'accept', invoke: () => validatePadInput({ ...owner, profile: square(), length: positive(), direction: [0, 0, 1] }) }
    case 13: return { name: 'self-intersecting-pad', expected: 'reject', invoke: () => validatePadInput({ ...owner, profile: { outer: [[0, 0, 0], [2, 2, 0], [0, 2, 0], [2, 0, 0]] }, length: positive() }) }
    case 14: return { name: 'future-pocket-base', expected: 'reject', invoke: () => validatePocketInput({ ...owner, base: { ...base, documentVersion: owner.documentVersion + 1 }, profile: square(), length: positive() }) }
    case 15: return { name: 'zero-revolution-axis', expected: 'reject', invoke: () => validateRevolutionInput({ ...owner, profile: square(), axisDirection: [0, 0, 0], angle: 360 }) }
    case 16: return { name: 'invalid-groove-angle', expected: 'reject', invoke: () => validateGrooveInput({ ...owner, base, profile: square(), axisDirection: [0, 1, 0], angle: 361 }) }
    case 17: return { name: 'valid-step-import', expected: 'accept', invoke: () => validateGeometryFileImport({ ...owner, format: 'step', text: `ISO-10303-21;\n/* ${random().toString(16)} */\nEND-ISO-10303-21;` }) }
    case 18: return { name: 'unsupported-import-format', expected: 'reject', invoke: () => validateGeometryFileImport({ ...owner, format: 'stl', text: 'solid fuzz' } as never) }
    case 19: return { name: 'zero-mirror-normal', expected: 'reject', invoke: () => validateMirrorInput({ ...owner, shape: base, origin: [0, 0, 0], normal: [0, 0, 0] }) }
    case 20: return { name: 'valid-standalone-loft', expected: 'accept', invoke: () => validateLoftInput({ ...owner, sections: [square(), elevatedSquare()] }) }
    case 21: return { name: 'valid-additive-loft', expected: 'accept', invoke: () => validateLoftInput({ ...owner, sections: [square(), elevatedSquare()], mode: 'additive', base }) }
    case 22: return { name: 'short-loft-sections', expected: 'reject', invoke: () => validateLoftInput({ ...owner, sections: [square()] }) }
    case 23: return { name: 'loft-hole-unsupported', expected: 'reject', invoke: () => validateLoftInput({ ...owner, sections: [{ ...square(), holes: [square(0.1).outer] }, square()] }) }
    case 24: return { name: 'valid-standalone-pipe', expected: 'accept', invoke: () => validatePipeInput({ ...owner, profile: square(), path: [[0, 0, 0], [positive(), 0, positive()], [positive(), positive(), positive()]] }) }
    case 25: return { name: 'valid-subtractive-pipe', expected: 'accept', invoke: () => validatePipeInput({ ...owner, profile: square(), path: [[0, 0, 0], [0, positive(), positive()]], mode: 'subtractive', base }) }
    case 26: return { name: 'duplicate-pipe-path-point', expected: 'reject', invoke: () => validatePipeInput({ ...owner, profile: square(), path: [[0, 0, 0], [0, 0, 0]] }) }
    case 27: return { name: 'pipe-hole-unsupported', expected: 'reject', invoke: () => validatePipeInput({ ...owner, profile: { ...square(), holes: [square(0.1).outer] }, path: [[0, 0, 0], [0, 0, positive()]] }) }
    case 28: return { name: 'valid-torus', expected: 'accept', invoke: () => validateTorusInput({ ...owner, majorRadius: positive(), minorRadius: positive(), direction: [0, 0, 1], angle: random() % 360 + 1 }) }
    default: return { name: 'invalid-torus-radius', expected: 'reject', invoke: () => validateTorusInput({ ...owner, majorRadius: positive(), minorRadius: invalidScalar() }) }
  }
}

const durations: number[] = []
const categories = new Map<string, number>()
const counts = { accepted: 0, rejected: 0, validCases: 0, invalidCases: 0 }
for (let index = 0; index < 2000; index += 1) {
  const fuzzCase = createCase(index)
  categories.set(fuzzCase.name, (categories.get(fuzzCase.name) ?? 0) + 1)
  const started = performance.now()
  let outcome: 'accept' | 'reject' = 'accept'
  let caught: unknown
  try {
    fuzzCase.invoke()
  } catch (error) {
    outcome = 'reject'
    caught = error
  }
  durations.push(performance.now() - started)
  counts[outcome === 'accept' ? 'accepted' : 'rejected'] += 1
  counts[fuzzCase.expected === 'accept' ? 'validCases' : 'invalidCases'] += 1
  assert.equal(outcome, fuzzCase.expected, `Geometry fuzz category ${fuzzCase.name} produced ${outcome}, expected ${fuzzCase.expected}.`)
  if (outcome === 'reject') assert.ok(caught instanceof Error, `Geometry fuzz category ${fuzzCase.name} rejected with a non-Error value.`)
}

durations.sort((left, right) => left - right)
const p95Ms = durations[Math.floor(durations.length * 0.95)]
assert.ok(p95Ms < 10, `Geometry input fuzz p95 exceeded 10 ms: ${p95Ms.toFixed(3)} ms.`)
const report = {
  status: 'geometry-input-fuzz-pass',
  seed: '0x9e3779b9',
  cases: durations.length,
  counts,
  categories: Object.fromEntries([...categories].sort(([left], [right]) => left.localeCompare(right))),
  timing: { p95Ms: Number(p95Ms.toFixed(3)), maxMs: Number(durations.at(-1)?.toFixed(3)) },
}
await writeFile(new URL('../config/qa04-geometry-fuzz-verification.json', import.meta.url), `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify(report, null, 2))
