import { createHash } from 'node:crypto'
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'

const root = resolve(new URL('..', import.meta.url).pathname)
const readBytes = (relativePath) => readFile(resolve(root, relativePath))
const readJson = async (relativePath) => JSON.parse(await readFile(resolve(root, relativePath), 'utf8'))
const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex')
const assertEqual = (actual, expected, label) => {
  if (actual !== expected) throw new Error(`${label} drifted: expected ${expected}, got ${actual}.`)
}

const baseline = await readJson('config/occt-upstream-drift-baseline.json')
const matrix = await readJson('config/compatibility-matrix.json')
const packageJson = await readJson('node_modules/@bitbybit-dev/occt/package.json')
const goldenManifestBytes = await readBytes('fixtures/freecad-golden/manifest.json')
const goldenManifest = JSON.parse(goldenManifestBytes)
const threeWayBytes = await readBytes('config/freecad-bitbybit-native-threeway.json')
const threeWay = JSON.parse(threeWayBytes)

if (baseline.schemaVersion !== 1) throw new Error('Unsupported OCCT upstream drift baseline schema.')
assertEqual(packageJson.version, baseline.bitbybit.version, 'Bitbybit package version')
assertEqual(matrix.bitbybitSource.commit, baseline.bitbybit.sourceCommit, 'Bitbybit source commit')
assertEqual(matrix.bitbybitSource.wasmArtifact.sha256, baseline.bitbybit.wasmSha256, 'Bitbybit declared WASM hash')
assertEqual(sha256(await readBytes(matrix.bitbybitSource.wasmArtifact.path)), baseline.bitbybit.wasmSha256, 'Bitbybit installed WASM hash')

const declarations = await readFile(resolve(root, baseline.bitbybit.declarationPath), 'utf8')
const canonicalSurface = baseline.bitbybit.surfaceInterfaces.map((name) => {
  const match = declarations.match(new RegExp(`export interface ${name} extends ClassHandle \\{([\\s\\S]*?)\\n\\}`))
  if (!match) throw new Error(`Bitbybit ABI surface is missing ${name}.`)
  return `${name}:${match[1].replace(/\s+/g, ' ').trim()}`
}).join('\n')
assertEqual(sha256(canonicalSurface), baseline.bitbybit.surfaceSha256, 'Bitbybit declaration surface')

assertEqual(matrix.nativeOcctHistory.occtVersion, baseline.nativeOcct.version, 'Native OCCT version')
assertEqual(matrix.nativeOcctHistory.sourceCommit, baseline.nativeOcct.sourceCommit, 'Native OCCT source commit')
assertEqual(matrix.nativeOcctHistory.freecadNamingBuild.status, baseline.nativeOcct.buildStatus, 'Native OCCT naming build status')
for (const [kind, relativePath, expectedHash] of [
  ['JavaScript', matrix.nativeOcctHistory.artifact.javascript, baseline.nativeOcct.javascriptSha256],
  ['WASM', matrix.nativeOcctHistory.artifact.wasm, baseline.nativeOcct.wasmSha256],
  ['DATA', matrix.nativeOcctHistory.artifact.data, baseline.nativeOcct.dataSha256],
]) {
  assertEqual(sha256(await readBytes(relativePath)), expectedHash, `Native OCCT ${kind} artifact`)
}

assertEqual(goldenManifest.scenarios.length, baseline.golden.manifestScenarioCount, 'Golden manifest scenario count')
assertEqual(sha256(goldenManifestBytes), baseline.golden.manifestSha256, 'Golden manifest')
assertEqual(threeWay.scenarioCount, baseline.golden.threeWayScenarioCount, 'Three-way scenario count')
assertEqual(sha256(threeWayBytes), baseline.golden.threeWayReportSha256, 'Three-way golden report')
const operationCounts = Object.fromEntries(['cut', 'fuse', 'common'].map((operation) => [operation, threeWay.reports.filter((entry) => entry.operation === operation).length]))
for (const [operation, expectedCount] of Object.entries(baseline.golden.operationCounts)) assertEqual(operationCounts[operation], expectedCount, `${operation} scenario count`)
for (const entry of threeWay.reports) {
  if (entry.bitbybit.differences.differences.length || entry.native.differences.differences.length) throw new Error(`Unknown golden drift in ${entry.id}.`)
}

console.log(JSON.stringify({
  status: 'occt-upstream-drift-pass',
  bitbybit: { version: packageJson.version, sourceCommit: baseline.bitbybit.sourceCommit, surfaceSha256: baseline.bitbybit.surfaceSha256 },
  nativeOcct: { version: baseline.nativeOcct.version, sourceCommit: baseline.nativeOcct.sourceCommit, buildStatus: baseline.nativeOcct.buildStatus },
  golden: { manifestScenarios: goldenManifest.scenarios.length, threeWayScenarios: threeWay.scenarioCount, operationCounts }
}, null, 2))
