import { existsSync, mkdtempSync, rmSync } from 'node:fs'
import { readFile, writeFile } from 'node:fs/promises'
import { spawnSync } from 'node:child_process'
import { join, resolve } from 'node:path'
import { tmpdir } from 'node:os'

const root = resolve(new URL('..', import.meta.url).pathname)
const executable = process.env.FREECAD_CMD || resolve(root, '.cache/freecad/install-desktop/bin/FreeCADCmd')
const sysroot = resolve(root, '.cache/freecad/sysroot')
const scriptPath = resolve(root, 'scripts/freecad-property-direction-failure.py')
const outputPath = resolve(root, 'config/freecad-property-direction-failure.json')
if (!existsSync(executable)) throw new Error(`FreeCAD PropertyDirection executable is missing: ${executable}`)
const baseEnv = {
    ...process.env,
    PYTHONPATH: `${resolve(sysroot, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`,
    LD_LIBRARY_PATH: `${resolve(sysroot, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`,
}
const runProbe = ({ mode = 'main', target = outputPath, timeout = 120_000 } = {}) => spawnSync(executable, ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), scriptPath], {
  cwd: root,
  encoding: 'utf8',
  timeout,
  maxBuffer: 32 * 1024 * 1024,
  env: {
    ...baseEnv,
    FREECAD_PROPERTY_DIRECTION_FAILURE_OUTPUT: target,
    FREECAD_PROPERTY_DIRECTION_FAILURE_MODE: mode,
  },
})
const execution = runProbe({ timeout: Number(process.env.FREECAD_PROPERTY_DIRECTION_FAILURE_TIMEOUT_MS || 120_000) })
const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
if (execution.error || execution.status !== 0 || !output.includes('FREECAD_PROPERTY_DIRECTION_FAILURE_RESULT=')) throw new Error(`FreeCAD PropertyDirection failure probe failed with status ${execution.status}: ${execution.error?.message || 'process failed'}\n${output.trim()}`)
const report = JSON.parse(await readFile(outputPath, 'utf8'))
const isolatedRoot = mkdtempSync(join(tmpdir(), 'freecad-property-direction-'))
try {
  report.nonFiniteConsumerBoundaries = []
  const isolatedTimeout = Number(process.env.FREECAD_PROPERTY_DIRECTION_NON_FINITE_TIMEOUT_MS || 5_000)
  for (const hostId of ['Mirroring', 'ProjectOnSurface']) {
    for (const valueId of ['nan', 'positive-infinity', 'negative-infinity']) {
      const caseId = `${hostId}-${valueId}`
      const attempts = []
      for (let attempt = 1; attempt <= 2; attempt += 1) {
        const target = join(isolatedRoot, `${caseId}-${attempt}.json`)
        const startedAt = Date.now()
        const isolated = runProbe({ mode: `non-finite:${hostId}:${valueId}`, target, timeout: isolatedTimeout })
        const isolatedOutput = `${isolated.stdout || ''}\n${isolated.stderr || ''}`.trim()
        const timedOut = isolated.error?.code === 'ETIMEDOUT'
        let completedCase = null
        if (!timedOut && isolated.status === 0 && isolatedOutput.includes('FREECAD_PROPERTY_DIRECTION_FAILURE_ISOLATED_RESULT=') && existsSync(target)) {
          completedCase = JSON.parse(await readFile(target, 'utf8')).case
        }
        attempts.push({
          attempt,
          timeoutMs: isolatedTimeout,
          elapsedMs: Date.now() - startedAt,
          timedOut,
          exitStatus: isolated.status,
          signal: isolated.signal,
          errorCode: isolated.error?.code || null,
          progressMarkerObserved: isolatedOutput.includes(`FREECAD_PROPERTY_DIRECTION_PROGRESS=${caseId}`),
          completionMarkerObserved: isolatedOutput.includes('FREECAD_PROPERTY_DIRECTION_FAILURE_ISOLATED_RESULT='),
          diagnostic: isolatedOutput.split('\n').filter((line) => line && !line.startsWith('FreeCAD 0.')).slice(-8),
          completedCase,
          outcome: timedOut ? 'timeout' : completedCase ? 'completed' : 'process-exited-without-completion',
        })
      }
      const outcomes = [...new Set(attempts.map(({ outcome }) => outcome))]
      const stable = outcomes.length === 1 && attempts.every(({ progressMarkerObserved }) => progressMarkerObserved)
      report.nonFiniteConsumerBoundaries.push({
        id: caseId,
        mode: `non-finite:${hostId}:${valueId}`,
        attemptCount: attempts.length,
        attempts,
        stable,
        classification: !stable
          ? 'native-nondeterministic'
          : outcomes[0] === 'timeout'
            ? 'native-synchronous-consumer-timeout'
            : outcomes[0] === 'process-exited-without-completion'
              ? 'native-process-exit-without-probe-completion'
              : 'native-consumer-returned',
      })
    }
  }
  report.status = report.nonFiniteConsumerBoundaries.every(({ stable, classification }) => stable && classification !== 'native-consumer-returned') ? 'pass' : 'fail'
  await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`)
} finally {
  rmSync(isolatedRoot, { recursive: true, force: true })
}
console.log(JSON.stringify({
  status: report.status,
  output: 'config/freecad-property-direction-failure.json',
  acceptedCoercions: report.setterBoundaries.acceptedCoercions.map(({ id, accepted, after }) => ({ id, accepted, after })),
  setterFailures: report.setterBoundaries.failures.map(({ id, exception, valuePreserved }) => ({ id, exception, valuePreserved })),
  consumerFailures: report.consumerFailures.map(({ id, setterException, recomputeResult, afterRecompute }) => ({ id, setterException, recomputeResult, state: afterRecompute.state })),
  nonFiniteConsumerBoundaries: report.nonFiniteConsumerBoundaries.map(({ id, classification, attemptCount, stable }) => ({ id, classification, attemptCount, stable })),
  dependencyFailures: report.dependencyFailures.map(({ id, recomputeResult, afterRecompute }) => ({ id, recomputeResult, state: afterRecompute.state })),
  transactionRestored: report.transaction.restored,
}, null, 2))
