import { createHash } from 'node:crypto'
import { readFile, writeFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import { PROJECT_SCHEMA_MIGRATIONS, PROJECT_SCHEMA_VERSION, runProjectSchemaMigrations, type ProjectMigrationTransaction } from '../src/facade/projectSchema'

const root = resolve(new URL('..', import.meta.url).pathname)
const outputPath = resolve(root, 'config/project-migration-verification.json')
const checkOnly = process.argv.includes('--check')
const migrationHash = createHash('sha256').update(PROJECT_SCHEMA_MIGRATIONS.map(({ version, sql }) => `${version}:${sql}`).join('\n')).digest('hex')

type RehearsalTransaction = ProjectMigrationTransaction & { applied: number[]; committed: boolean; rolledBack: boolean; executed: number[] }
const createTransaction = (initial: number[], failVersion?: number): RehearsalTransaction => {
  let working = new Set(initial)
  let snapshot = new Set(initial)
  const transaction: RehearsalTransaction = {
    applied: initial.slice(), committed: false, rolledBack: false, executed: [],
    begin: () => { snapshot = new Set(working) },
    isApplied: (version) => working.has(version),
    execute: (sql) => {
      const migration = PROJECT_SCHEMA_MIGRATIONS.find((candidate) => candidate.sql === sql)
      if (!migration) throw new Error('unknown migration SQL')
      transaction.executed.push(migration.version)
      if (migration.version === failVersion) throw new Error(`injected migration failure ${failVersion}`)
    },
    markApplied: (version) => { working.add(version); transaction.applied = [...working].sort((left, right) => left - right) },
    commit: () => { transaction.committed = true },
    rollback: () => { working = new Set(snapshot); transaction.applied = [...working].sort((left, right) => left - right); transaction.rolledBack = true },
  }
  return transaction
}

const versions = PROJECT_SCHEMA_MIGRATIONS.map(({ version }) => version)
const forward = versions.map((startVersion) => {
  const transaction = createTransaction(versions.filter((version) => version < startVersion))
  const applied = runProjectSchemaMigrations(transaction, PROJECT_SCHEMA_MIGRATIONS, 1700000000000)
  const expected = versions.filter((version) => version >= startVersion)
  if (JSON.stringify(applied) !== JSON.stringify(expected) || !transaction.committed || transaction.rolledBack || JSON.stringify(transaction.applied) !== JSON.stringify(versions)) throw new Error(`forward migration rehearsal failed at start ${startVersion}`)
  return { from: startVersion - 1, to: PROJECT_SCHEMA_VERSION, applied, committed: transaction.committed, rolledBack: transaction.rolledBack }
})
const rollback = versions.map((failVersion) => {
  const initial = versions.filter((version) => version < failVersion)
  const transaction = createTransaction(initial, failVersion)
  let failed = false
  try { runProjectSchemaMigrations(transaction, PROJECT_SCHEMA_MIGRATIONS, 1700000000000) } catch (error) { failed = error instanceof Error && error.message === `injected migration failure ${failVersion}` }
  if (!failed || transaction.committed || !transaction.rolledBack || JSON.stringify(transaction.applied) !== JSON.stringify(initial)) throw new Error(`rollback rehearsal failed at migration ${failVersion}`)
  return { failedAt: failVersion, restoredVersions: initial, committed: transaction.committed, rolledBack: transaction.rolledBack }
})
const rerun = createTransaction(versions)
const rerunApplied = runProjectSchemaMigrations(rerun, PROJECT_SCHEMA_MIGRATIONS, 1700000000000)
if (rerunApplied.length !== 0 || !rerun.committed || rerun.rolledBack) throw new Error('idempotent migration rehearsal failed')

const report = {
  schemaVersion: 1,
  status: 'pass',
  currentSchemaVersion: PROJECT_SCHEMA_VERSION,
  migrationVersions: versions,
  migrationSha256: migrationHash,
  forward,
  rollback,
  idempotentRerun: { applied: rerunApplied, committed: rerun.committed, rolledBack: rerun.rolledBack },
  recoveryPolicy: { onFailure: 'rollback-entire-transaction', unsupportedFutureVersion: 'reject-before-write' },
}
const serialized = `${JSON.stringify(report, null, 2)}\n`
if (checkOnly) {
  const existing = await readFile(outputPath, 'utf8').catch(() => '')
  if (existing !== serialized) throw new Error('Project migration rehearsal report is stale; run `npm run test:project-migration`.')
  console.log(JSON.stringify({ status: 'project-migration-pass', currentSchemaVersion: PROJECT_SCHEMA_VERSION, forwardCases: forward.length, rollbackCases: rollback.length, migrationSha256: migrationHash }, null, 2))
} else {
  await writeFile(outputPath, serialized)
  console.log(JSON.stringify({ status: 'project-migration-generated', output: outputPath, currentSchemaVersion: PROJECT_SCHEMA_VERSION, forwardCases: forward.length, rollbackCases: rollback.length, migrationSha256: migrationHash }, null, 2))
}
