import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { spawnSync } from 'node:child_process'
import { loadGoldenManifest } from './freecad-golden-contract.mjs'

const root = resolve(new URL('..', import.meta.url).pathname)
const scenarioDir = resolve(root, 'fixtures/freecad-golden/scenarios')
const failureDir = resolve(root, 'fixtures/freecad-golden/failures')
const workDir = resolve(root, '.cache/freecad/generated-fixtures')
const command = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-native/bin/FreeCADCmd')
const driver = resolve(root, 'scripts/freecad-golden-driver.py')
await mkdir(scenarioDir, { recursive: true })
await mkdir(failureDir, { recursive: true })
await mkdir(workDir, { recursive: true })

const manifest = await loadGoldenManifest(resolve(root, 'fixtures/freecad-golden/manifest.json'))
const existing = manifest.scenarios.map(({ scenario }) => scenario)
const generated = []
const add = (id, category, description, operation) => generated.push({ schemaVersion: 1, id, category, description, operation, tolerance: { linear: 1e-6, scalar: 1e-6 } })

for (let index = 1; index <= 20; index += 1) {
  add(`part-box-${String(index).padStart(3, '0')}`, 'Part', `Parametric box fixture ${index}`, {
    type: 'box', length: 2 + (index % 9), width: 3 + ((index * 2) % 11), height: 4 + ((index * 3) % 13),
    placement: { translation: [index % 5, (index * 2) % 7, index % 3] },
  })
}
for (let index = 1; index <= 20; index += 1) {
  add(`part-cylinder-${String(index).padStart(3, '0')}`, 'Part', `Parametric cylinder fixture ${index}`, {
    type: 'cylinder', radius: 1 + (index % 8) / 2, height: 5 + (index * 3) % 17, angle: 360,
    placement: { translation: [-(index % 4), (index * 2) % 5, index % 3] },
  })
}
for (let index = 1; index <= 15; index += 1) {
  add(`part-sphere-${String(index).padStart(3, '0')}`, 'Part', `Parametric sphere fixture ${index}`, {
    type: 'sphere', radius: 1 + (index % 9) / 3,
    placement: { translation: [index % 6, -(index % 5), (index * 2) % 4] },
  })
}
for (let index = 1; index <= 15; index += 1) {
  add(`part-cone-${String(index).padStart(3, '0')}`, 'Part', `Parametric cone fixture ${index}`, {
    type: 'cone', radius1: 2 + (index % 6), radius2: 0.5 + (index % 4) / 2, height: 4 + (index % 15), angle: 360,
    placement: { translation: [index % 4, index % 6, -(index % 2)] },
  })
}
for (let index = 1; index <= 25; index += 1) {
  const kind = index % 3 === 0 ? 'common' : index % 3 === 1 ? 'cut' : 'fuse'
  const size = 8 + (index % 5)
  const base = { type: 'box', length: size, width: size - 1, height: size + 1, placement: { translation: [0, 0, index % 2] } }
  const tool = kind === 'cut'
    ? { type: 'cylinder', radius: 1.5 + (index % 3), height: size + 1, angle: 360, placement: { translation: [size / 2, (size - 1) / 2, index % 2] } }
    : { type: 'box', length: 4 + (index % 4), width: 4 + ((index + 1) % 4), height: 4 + ((index + 2) % 4), placement: { translation: [2, 2, index % 2] } }
  add(`part-${kind}-${String(index).padStart(3, '0')}`, 'Part', `${kind} Boolean fixture ${index}`, { type: kind, base, tool })
}

const expectedFromOracle = (scenario) => {
  const tempFile = resolve(workDir, `${scenario.id}.json`)
  return writeFile(tempFile, `${JSON.stringify(scenario, null, 2)}\n`).then(() => {
    const execution = spawnSync(command, [driver], {
      cwd: root,
      encoding: 'utf8',
      timeout: 120000,
      maxBuffer: 4 * 1024 * 1024,
      env: { ...process.env, FREECAD_GOLDEN_SCENARIO: tempFile },
    })
    const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
    const line = output.split(/\r?\n/).find((entry) => entry.startsWith('FREECAD_GOLDEN_RESULT='))
    if (execution.error || execution.status !== 0 || !line) throw new Error(`${scenario.id} oracle failure: ${execution.error?.message || output.trim()}`)
    const actual = JSON.parse(line.slice('FREECAD_GOLDEN_RESULT='.length))
    if (!['Solid', 'Compound'].includes(actual.shapeType) || actual.isNull !== false || actual.isValid !== true || actual.solids < 1) throw new Error(`${scenario.id} produced an invalid success shape.`)
    return {
      ...scenario,
      expected: {
        shapeType: actual.shapeType,
        isNull: actual.isNull,
        isValid: actual.isValid,
        solids: actual.solids,
        faces: actual.faces,
        edges: actual.edges,
        vertices: actual.vertices,
        volume: actual.volume,
        area: actual.area,
        boundingBox: actual.boundingBox,
      },
    }
  })
}

const allSuccess = [...existing]
for (const scenario of generated) {
  const completed = await expectedFromOracle(scenario)
  allSuccess.push(completed)
  await writeFile(resolve(scenarioDir, `${completed.id}.json`), `${JSON.stringify(completed, null, 2)}\n`)
}
const successEntries = allSuccess.map((scenario) => ({ id: scenario.id, file: `scenarios/${scenario.id}.json` }))
await writeFile(resolve(root, 'fixtures/freecad-golden/manifest.json'), `${JSON.stringify({ schemaVersion: 1, baselineId: 'freecad-1.1.1', driver: 'scripts/freecad-golden-driver.py', scenarios: successEntries }, null, 2)}\n`)

const failures = []
const addFailure = (id, description, operation, expectedError) => failures.push({ schemaVersion: 1, id, category: 'Part', description, operation, expectedError })
for (let index = 1; index <= 12; index += 1) addFailure(`invalid-box-${String(index).padStart(3, '0')}`, 'Box dimension rejection', { type: 'box', length: index % 2 ? 0 : -index, width: 2 + index, height: 3 }, 'positive')
for (let index = 1; index <= 10; index += 1) addFailure(`invalid-cylinder-${String(index).padStart(3, '0')}`, 'Cylinder parameter rejection', { type: 'cylinder', radius: index % 2 ? 0 : -index, height: index % 3 ? 5 : 0, angle: index % 4 ? 360 : 361 }, 'parameters')
for (let index = 1; index <= 8; index += 1) addFailure(`invalid-sphere-${String(index).padStart(3, '0')}`, 'Sphere radius rejection', { type: 'sphere', radius: index % 2 ? 0 : -index }, 'positive')
for (let index = 1; index <= 8; index += 1) addFailure(`invalid-cone-${String(index).padStart(3, '0')}`, 'Cone parameter rejection', { type: 'cone', radius1: index % 2 ? -1 : 0, radius2: index % 2 ? 0 : -1, height: index % 3 ? 5 : 0, angle: index % 4 ? 360 : 361 }, 'parameters')
for (let index = 1; index <= 6; index += 1) addFailure(`invalid-boolean-${String(index).padStart(3, '0')}`, 'Boolean operand rejection', { type: index % 2 ? 'cut' : 'fuse', base: { type: 'box', length: 1, width: 1, height: 1 }, ...(index % 3 ? { tool: { type: 'unknown' } } : {}) }, index % 3 ? 'Unsupported' : 'operation')
addFailure('invalid-common-001', 'Common operand rejection', { type: 'common', base: { type: 'box', length: 1, width: 1, height: 1 } }, 'operation')
for (let index = 1; index <= 6; index += 1) addFailure(`invalid-operation-${String(index).padStart(3, '0')}`, 'Unknown operation rejection', { type: `unsupported-${index}` }, 'Unsupported')
for (const failure of failures) await writeFile(resolve(failureDir, `${failure.id}.json`), `${JSON.stringify(failure, null, 2)}\n`)
await writeFile(resolve(failureDir, 'manifest.json'), `${JSON.stringify({ schemaVersion: 1, baselineId: 'freecad-1.1.1', driver: 'scripts/freecad-golden-driver.py', failures: failures.map((failure) => ({ id: failure.id, file: `${failure.id}.json` })) }, null, 2)}\n`)
console.log(JSON.stringify({ status: 'freecad-golden-fixtures-generated', successCount: allSuccess.length, generatedSuccessCount: generated.length, failureCount: failures.length }, null, 2))
