import { isDeepStrictEqual } from 'node:util'
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'

const root = resolve(new URL('..', import.meta.url).pathname)
const report = JSON.parse(await readFile(resolve(root, 'config/freecad-property-fileincluded-failure.json'), 'utf8'))
const fail = (message) => { throw new Error(`FreeCAD PropertyFileIncluded failure check failed: ${message}`) }
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-fileincluded-failure' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.propertyType !== 'App::PropertyFileIncluded' || report.object?.typeId !== 'App::DocumentObjectFileIncluded' || report.property?.name !== 'File' || report.property?.typeId !== 'App::PropertyFileIncluded') fail('baseline or identity is invalid')
if (report.initial?.exists !== true || report.initial.isFile !== true || report.initial.isDirectory !== false || report.initial.writeBits !== 0 || report.initial.propertyTypeId !== 'App::PropertyFileIncluded' || !isDeepStrictEqual(report.initial.propertyStatus, []) || !isDeepStrictEqual(report.initial.editorMode, []) || !isDeepStrictEqual(report.initial.objectState, ['Up-to-date']) || report.initial.statusString !== 'Valid') fail('initial included-file state is invalid')

const expectedFailures = new Map([
  ['missing-path', ['OSError', 'does not exist']],
  ['wrong-type', ['TypeError', 'Type must be string or file']],
  ['tuple-wrong-arity', ['TypeError', 'Tuple needs size of (filePath,newFileName)']],
  ['tuple-wrong-name-type', ['TypeError', 'Second item in tuple must be a string']],
  ['dictionary-wrong-filename-type', ['TypeError', 'PyCXX: Error creating object']],
  ['same-current-transient', ['OSError', 'Not possible to set the same file!']],
])
if (report.failures?.length !== expectedFailures.size) fail('failure case count changed')
for (const entry of report.failures) {
  const expected = expectedFailures.get(entry.id)
  if (!expected || entry.assignmentException?.type !== expected[0] || !entry.assignmentException.message.includes(expected[1]) || entry.valuePreserved !== true || entry.objectsPreserved !== true || !isDeepStrictEqual(entry.before.objectSet, entry.after.objectSet) || entry.before.value !== entry.after.value || entry.before.sha256 !== entry.after.sha256 || entry.after.writeBits !== 0 || entry.after.exists !== true) fail(`${entry.id} rejection polluted the included resource or document`)
}

const [directory] = report.acceptedRisks ?? []
if (report.acceptedRisks?.length !== 1 || directory?.id !== 'directory-path' || directory.assignmentException !== null || directory.oldFileRemoved !== true || directory.directoryAccepted !== true || directory.objectsPreserved !== true || directory.before?.isFile !== true || directory.after?.isFile !== false || directory.after?.isDirectory !== true || directory.after.pathExists !== true || directory.recovery?.isFile !== true || directory.recovery.isDirectory !== false || directory.recovery.writeBits !== 0 || directory.recovery.statusString !== 'Valid') fail('directory-path non-atomic acceptance or recovery changed')

const filterOnly = report.filterOnly
if (filterOnly?.exception !== null || filterOnly.valuePreserved !== true || filterOnly.objectsPreserved !== true || filterOnly.before.value !== filterOnly.after.value || filterOnly.before.sha256 !== filterOnly.after.sha256) fail('filter-only dictionary boundary changed')
const editor = report.editorReadOnly
if (!isDeepStrictEqual(editor?.editorMode, ['ReadOnly']) || editor.exception !== null || editor.pythonBypassesEditorReadOnly !== true || editor.after.exists !== true || editor.after.writeBits !== 0 || editor.after.baseName !== 'editor-write.bin') fail('editor ReadOnly did not preserve Python setter behavior')
const immutable = report.immutable
if (!isDeepStrictEqual(immutable?.status, ['Immutable']) || immutable.exception?.type !== 'AttributeError' || immutable.exception.message !== "Object attribute 'File' is read-only" || immutable.before.value !== immutable.after.value || immutable.before.sha256 !== immutable.after.sha256 || !isDeepStrictEqual(immutable.restoredStatus, [])) fail('Immutable rejection or status restoration changed')

const transaction = report.transaction
if (transaction?.undoMode !== 1 || transaction.pendingAfterEdit !== true || transaction.pendingAfterAbort !== false || transaction.activeAfterEdit?.name !== 'property-fileincluded-cancel' || !(transaction.activeAfterEdit.id > 0) || transaction.activeAfterAbort?.name !== '' || transaction.activeAfterAbort.id !== 0 || transaction.restored !== true || transaction.editedPathExistsAfterAbort !== false || !isDeepStrictEqual(transaction.objectsBefore, transaction.objectsAfter) || transaction.edited?.baseName !== 'transaction-write.bin' || transaction.edited.writeBits !== 0 || transaction.afterAbort.writeBits !== 0 || transaction.before.sha256 === transaction.edited.sha256 || transaction.before.sha256 !== transaction.afterAbort.sha256) fail('transaction abort did not restore ownership and remove the edited transient copy')
if (report.cancellationBoundary?.supported !== false || report.cancellationBoundary.classification !== 'synchronous-property-setter' || report.cancellationBoundary.reason !== 'no-native-cancel-hook' || report.cancellationBoundary.replacement !== 'abort-active-document-transaction') fail('cancellation boundary is not explicit')
if (report.documentIntegrity?.objectsPreserved !== true || report.documentIntegrity.objectCount !== 1 || !isDeepStrictEqual(report.documentIntegrity.initialObjects, report.documentIntegrity.finalObjects)) fail('failure cases polluted the document object set')

console.log(JSON.stringify({ status: 'freecad-property-fileincluded-failure-pass', failures: report.failures.map(({ id, assignmentException, valuePreserved }) => ({ id, type: assignmentException.type, message: assignmentException.message, valuePreserved })), acceptedNonAtomicRisk: { id: directory.id, oldFileRemoved: directory.oldFileRemoved, directoryAccepted: directory.directoryAccepted, recovered: directory.recovery.isFile }, filterOnlyNoOp: filterOnly.valuePreserved, editorReadOnlyPythonBypass: editor.pythonBypassesEditorReadOnly, immutable: immutable.exception, transactionRestored: transaction.restored, editedTransientRemoved: !transaction.editedPathExistsAfterAbort, cancellationBoundary: report.cancellationBoundary }, null, 2))
