#!/usr/bin/env node

import { createHash } from 'node:crypto'
import { readFileSync, writeFileSync } from 'node:fs'

const shaPattern = /^[0-9a-f]{40}$/
const digestImagePattern = /^[A-Za-z0-9.-]+(?::[0-9]+)?(?:\/[A-Za-z0-9._-]+)+@sha256:[0-9a-f]{64}$/
const allowedComponents = new Set(['api', 'web'])
const smokeChecks = [
  'health',
  'readiness',
  'openapi',
  'register',
  'login',
  'apiKey',
  'models',
  'chatCompletions',
  'responses',
  'images',
  'web',
]

function requireExactKeys(value, keys, path) {
  const actual = Object.keys(value).sort()
  const expected = [...keys].sort()
  if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
    fail(`${path} has unexpected or missing fields`)
  }
}

function releaseIntegrity(manifest) {
  const payload = structuredClone(manifest)
  delete payload.integrity
  delete payload.deployment
  return createHash('sha256').update(JSON.stringify(payload)).digest('hex')
}

function fail(message) {
  console.error(`release_manifest=FAIL ${message}`)
  process.exit(1)
}

function load(path) {
  try {
    return JSON.parse(readFileSync(path, 'utf8'))
  } catch (error) {
    fail(`cannot read JSON manifest: ${error.message}`)
  }
}

function validate(manifest) {
  if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
    fail('manifest root must be an object')
  }
  const rootKeys = [
    'schemaVersion', 'releaseId', 'sourceSha', 'baseReleaseSha', 'sourceRef',
    'createdAt', 'buildCompletedAt', 'targetPlatform', 'components',
    'migrationsChanged', 'images', 'smoke', 'integrity',
  ]
  if (manifest.deployment !== undefined) rootKeys.push('deployment')
  requireExactKeys(manifest, rootKeys, 'manifest')
  if (manifest.schemaVersion !== 1) fail('schemaVersion must be 1')
  if (!shaPattern.test(manifest.sourceSha || '')) fail('sourceSha must be a full lowercase Git SHA')
  if (manifest.releaseId !== manifest.sourceSha) fail('releaseId must equal sourceSha')
  if (manifest.sourceRef !== 'refs/heads/main') fail('sourceRef must be refs/heads/main')
  if (manifest.baseReleaseSha !== null && !shaPattern.test(manifest.baseReleaseSha || '')) {
    fail('baseReleaseSha must be null or a full lowercase Git SHA')
  }
  if (manifest.baseReleaseSha === manifest.sourceSha) fail('baseReleaseSha must differ from sourceSha')
  if (manifest.targetPlatform !== 'linux/amd64') fail('targetPlatform must be linux/amd64')
  if (!Number.isFinite(Date.parse(manifest.createdAt))) fail('createdAt must be an ISO timestamp')
  if (!Number.isFinite(Date.parse(manifest.buildCompletedAt))) {
    fail('buildCompletedAt must be an ISO timestamp')
  }
  if (!Array.isArray(manifest.components) || manifest.components.length < 1 || manifest.components.length > 2) {
    fail('components must contain api, web, or both')
  }
  const uniqueComponents = [...new Set(manifest.components)]
  if (uniqueComponents.length !== manifest.components.length ||
      uniqueComponents.some((component) => !allowedComponents.has(component))) {
    fail('components contains an invalid or duplicate value')
  }
  if (typeof manifest.migrationsChanged !== 'boolean') fail('migrationsChanged must be boolean')
  if (manifest.migrationsChanged && !manifest.components.includes('api')) {
    fail('migration changes require the api component')
  }
  if (!manifest.images || typeof manifest.images !== 'object') fail('images must be an object')
  requireExactKeys(manifest.images, ['api', 'web'], 'images')
  for (const component of allowedComponents) {
    if (!digestImagePattern.test(manifest.images[component] || '')) {
      fail(`images.${component} must be a registry digest reference`)
    }
  }
  if (!manifest.smoke || manifest.smoke.status !== 'passed' || manifest.smoke.mode !== 'simulation') {
    fail('smoke must record a passed simulation check')
  }
  requireExactKeys(manifest.smoke, ['status', 'mode', 'completedAt', 'checks'], 'smoke')
  if (!Number.isFinite(Date.parse(manifest.smoke.completedAt))) {
    fail('smoke.completedAt must be an ISO timestamp')
  }
  if (!Array.isArray(manifest.smoke.checks) ||
      manifest.smoke.checks.length !== smokeChecks.length ||
      manifest.smoke.checks.some((check, index) => check !== smokeChecks[index])) {
    fail('smoke.checks is incomplete or out of order')
  }
  if (!manifest.integrity || typeof manifest.integrity !== 'object') {
    fail('integrity must be an object')
  }
  requireExactKeys(manifest.integrity, ['algorithm', 'value'], 'integrity')
  if (manifest.integrity.algorithm !== 'sha256' || !/^[0-9a-f]{64}$/.test(manifest.integrity.value || '')) {
    fail('integrity must contain a SHA-256 value')
  }
  if (releaseIntegrity(manifest) !== manifest.integrity.value) {
    fail('manifest content integrity check failed')
  }
  if (manifest.deployment !== undefined) {
    if (!manifest.deployment || typeof manifest.deployment !== 'object') {
      fail('deployment must be an object')
    }
    requireExactKeys(manifest.deployment, ['status', 'action', 'deployedAt', 'durationSeconds'], 'deployment')
    if (manifest.deployment.status !== 'passed' ||
        !['deploy', 'rollback'].includes(manifest.deployment.action) ||
        !Number.isFinite(Date.parse(manifest.deployment.deployedAt)) ||
        !Number.isInteger(manifest.deployment.durationSeconds) ||
        manifest.deployment.durationSeconds < 0) {
      fail('deployment record is invalid')
    }
  }

  const forbiddenKey = /(password|secret|token|credential|private.?key)/i
  const visit = (value, path = '') => {
    if (!value || typeof value !== 'object') return
    for (const [key, child] of Object.entries(value)) {
      const childPath = path ? `${path}.${key}` : key
      if (forbiddenKey.test(key)) fail(`forbidden sensitive field: ${childPath}`)
      visit(child, childPath)
    }
  }
  visit(manifest)
  return manifest
}

function requireEnv(name, pattern) {
  const value = process.env[name] || ''
  if (!value || (pattern && !pattern.test(value))) fail(`invalid or missing ${name}`)
  return value
}

const [command = 'validate', path, field] = process.argv.slice(2)

if (command === 'create') {
  if (!path) fail('create requires an output path')
  const sourceSha = requireEnv('RELEASE_SOURCE_SHA', shaPattern)
  const baseReleaseSha = process.env.RELEASE_BASE_SHA || null
  if (baseReleaseSha !== null && !shaPattern.test(baseReleaseSha)) fail('invalid RELEASE_BASE_SHA')
  const components = requireEnv('RELEASE_COMPONENTS').split(',').filter(Boolean)
  const migrationsChanged = process.env.RELEASE_MIGRATIONS_CHANGED === 'true'
  const manifest = {
    schemaVersion: 1,
    releaseId: sourceSha,
    sourceSha,
    baseReleaseSha,
    sourceRef: 'refs/heads/main',
    createdAt: new Date().toISOString(),
    buildCompletedAt: requireEnv('RELEASE_BUILD_COMPLETED_AT'),
    targetPlatform: 'linux/amd64',
    components,
    migrationsChanged,
    images: {
      api: requireEnv('RELEASE_API_IMAGE', digestImagePattern),
      web: requireEnv('RELEASE_WEB_IMAGE', digestImagePattern),
    },
    smoke: {
      status: 'passed',
      mode: 'simulation',
      completedAt: requireEnv('RELEASE_SMOKE_COMPLETED_AT'),
      checks: smokeChecks,
    },
    integrity: { algorithm: 'sha256', value: '' },
  }
  manifest.integrity.value = releaseIntegrity(manifest)
  validate(manifest)
  writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\n`, { flag: 'wx', mode: 0o600 })
  const digest = createHash('sha256').update(readFileSync(path)).digest('hex')
  console.log(`release_manifest=CREATED path=${path} sha256=${digest}`)
  process.exit(0)
}

if (!path) fail(`${command} requires a manifest path`)
const manifest = validate(load(path))

if (command === 'record-deployment') {
  if (!field) fail('record-deployment requires an output path')
  const deployedAt = requireEnv('RELEASE_DEPLOYED_AT')
  const durationSeconds = Number(process.env.RELEASE_DEPLOY_DURATION_SECONDS || '')
  const action = process.env.RELEASE_DEPLOY_ACTION || 'deploy'
  if (!Number.isFinite(Date.parse(deployedAt))) fail('invalid RELEASE_DEPLOYED_AT')
  if (!Number.isInteger(durationSeconds) || durationSeconds < 0) {
    fail('invalid RELEASE_DEPLOY_DURATION_SECONDS')
  }
  if (!['deploy', 'rollback'].includes(action)) fail('invalid RELEASE_DEPLOY_ACTION')
  manifest.deployment = { status: 'passed', action, deployedAt, durationSeconds }
  validate(manifest)
  writeFileSync(field, `${JSON.stringify(manifest, null, 2)}\n`, { flag: 'wx', mode: 0o600 })
  console.log(`release_manifest=RECORDED release=${manifest.releaseId} action=${action}`)
  process.exit(0)
}

if (command === 'validate') {
  const digest = createHash('sha256').update(readFileSync(path)).digest('hex')
  console.log(`release_manifest=PASS release=${manifest.releaseId} sha256=${digest}`)
  process.exit(0)
}

if (command === 'get') {
  if (!field || !/^[A-Za-z][A-Za-z0-9]*(?:\.[A-Za-z][A-Za-z0-9]*)*$/.test(field)) {
    fail('get requires a safe dotted field path')
  }
  let value = manifest
  for (const segment of field.split('.')) value = value?.[segment]
  if (value === null || value === undefined) console.log('')
  else if (typeof value === 'object') console.log(JSON.stringify(value))
  else console.log(String(value))
  process.exit(0)
}

fail(`unknown command: ${command}`)
