import fs from 'node:fs'
import { spawnSync } from 'node:child_process'

const source = JSON.parse(fs.readFileSync('contracts/fields/kbx.fields.json', 'utf8'))
const errors = []
const seen = new Set()
const aliasOwners = new Map()
const types = new Set(['text','code','integer','decimal','quantity','money','date','datetime','boolean','lookup','status'])

if (!/^\d+\.\d+\.\d+$/.test(source.version ?? '')) errors.push('field dictionary version must be semver')
for (const field of source.fields ?? []) {
  if (!/^[a-z][A-Za-z0-9]*$/.test(field.key ?? '')) errors.push(`invalid field key: ${field.key}`)
  if (seen.has(field.key)) errors.push(`duplicate field key: ${field.key}`)
  seen.add(field.key)
  if (!types.has(field.dataType)) errors.push(`invalid data type: ${field.key} ${field.dataType}`)
  if ((field.dataType === 'money' || field.dataType === 'quantity' || field.dataType === 'decimal') && (field.precision == null || field.scale == null))
    errors.push(`${field.key}: numeric field requires precision and scale`)
  if (field.dataType === 'lookup' && !field.lookupEntity) errors.push(`${field.key}: lookup field requires lookupEntity`)
  if (field.readonly && field.importable) errors.push(`${field.key}: readonly field cannot be importable`)
  if (field.sensitive && !field.masking) errors.push(`${field.key}: sensitive field requires masking metadata`)
  if (field.deprecated && !field.replacementKey) errors.push(`${field.key}: deprecated field requires replacementKey`)
  for (const alias of [field.key, ...(field.aliases ?? [])]) {
    const normalized = alias.trim().toLocaleLowerCase('ko-KR')
    if (!normalized) continue
    const owner = aliasOwners.get(normalized)
    // Collision is only dangerous when both fields can participate in generic import mapping.
    if (owner && owner !== field.key) {
      const other = source.fields.find(x => x.key === owner)
      if (field.importable && other?.importable) errors.push(`import alias collision: "${alias}" => ${owner}, ${field.key}`)
    } else aliasOwners.set(normalized, field.key)
  }
}

const result = spawnSync(process.execPath, ['scripts/generate-field-contracts.mjs'], { stdio: 'inherit' })
if (result.status !== 0) process.exit(result.status ?? 1)
const manifest = JSON.parse(fs.readFileSync('generated/field-manifest.json', 'utf8'))
if (manifest.count !== source.fields.length) errors.push('generated field manifest count drift')

const ts = fs.readFileSync('packages/kbx-contracts/src/generated/fieldCatalog.ts', 'utf8')
const cs = fs.readFileSync('backend/Shared/Contracts/Generated/KbxFieldCatalog.g.cs', 'utf8')
for (const field of source.fields) {
  if (!ts.includes(JSON.stringify(field.key))) errors.push(`TS field missing: ${field.key}`)
  if (!cs.includes(`"${field.key}"`)) errors.push(`C# field missing: ${field.key}`)
}


const orderImportTs = fs.readFileSync('apps/web/src/modules/oms/orders/import/order-import.definition.ts', 'utf8')
const orderImportCs = fs.readFileSync('backend/Modules/OMS/Orders/Import/OrderImportDefinition.cs', 'utf8')
if (!orderImportTs.includes("kbxImportField('orderQty')")) errors.push('OMS order import must consume canonical orderQty metadata on frontend')
if (!orderImportCs.includes('I(KbxFieldKeys.OrderQty)')) errors.push('OMS order import must consume canonical orderQty metadata on backend')
if (/\bfield:\s*['"]quantity['"]/.test(fs.readFileSync('apps/web/src/modules/oms/orders/register/order-register.definition.ts','utf8'))) errors.push('OMS order register still uses deprecated quantity field key')
if (!fs.existsSync('backend/Shared/Contracts/KbxFieldOpenApiSchemaFilter.cs')) errors.push('OpenAPI field contract filter is missing')

if (errors.length) {
  console.error('field governance FAIL')
  for (const e of errors) console.error(`- ${e}`)
  process.exit(1)
}
const adoption = spawnSync(process.execPath, ['scripts/report-field-adoption.mjs'], { stdio: 'inherit' })
if (adoption.status !== 0) process.exit(adoption.status ?? 1)
console.log(`field governance PASS: ${source.fields.length} canonical fields`)
