import fs from 'node:fs'
import path from 'node:path'

function normalizeTrimmed(value) {
  return String(value || '').trim()
}

function normalizeBoolean(value, fallback = false) {
  if (typeof value === 'boolean') {
    return value
  }

  const normalized = normalizeTrimmed(value).toLowerCase()
  if (!normalized) {
    return fallback
  }

  return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on'
}

function requireNonEmpty(value, label) {
  const normalized = normalizeTrimmed(value)
  if (!normalized) {
    throw new Error(`${label} is required.`)
  }
  return normalized
}

function requirePositiveInteger(value, label) {
  const parsed = Number.parseInt(String(value || ''), 10)
  if (!Number.isInteger(parsed) || parsed <= 0) {
    throw new Error(`${label} must be a positive integer.`)
  }
  return parsed
}

function normalizeArchiveSource(value, fallback = 'git-head') {
  const normalized = normalizeTrimmed(value).toLowerCase()
  if (normalized === 'worktree') {
    return 'worktree'
  }
  if (normalized === 'git' || normalized === 'head' || normalized === 'git-head') {
    return 'git-head'
  }
  return fallback
}

function maskSecretValue(value) {
  const normalized = String(value ?? '')
  if (!normalized) {
    return ''
  }

  if (normalized.length <= 4) {
    return '****'
  }

  return `${normalized.slice(0, 2)}***${normalized.slice(-2)}`
}

function maskManifestSecrets(value, key = '') {
  if (Array.isArray(value)) {
    return value.map((item) => maskManifestSecrets(item))
  }

  if (value && typeof value === 'object') {
    return Object.fromEntries(
      Object.entries(value).map(([entryKey, entryValue]) => [entryKey, maskManifestSecrets(entryValue, entryKey)]),
    )
  }

  if (typeof value === 'string' && /secret/i.test(key)) {
    return maskSecretValue(value)
  }

  return value
}

export function parseStagingProofArgs(argv) {
  const options = {
    manifest: '',
    identityFile: '',
    vpsHost: '212.227.13.220',
    vpsUser: 'dev',
    archiveSource: 'git-head',
    remoteDir: '',
    port: '3011',
    waitSeconds: '20',
    keepRemoteDir: false,
    json: false,
    dryRun: false,
  }

  for (let index = 0; index < argv.length; index += 1) {
    const argument = argv[index]
    const next = argv[index + 1] || ''

    switch (argument) {
      case '--manifest':
        options.manifest = next
        index += 1
        break
      case '--identity-file':
        options.identityFile = next
        index += 1
        break
      case '--vps-host':
        options.vpsHost = next
        index += 1
        break
      case '--vps-user':
        options.vpsUser = next
        index += 1
        break
      case '--archive-source':
        options.archiveSource = next
        index += 1
        break
      case '--remote-dir':
        options.remoteDir = next
        index += 1
        break
      case '--port':
        options.port = next
        index += 1
        break
      case '--wait-seconds':
        options.waitSeconds = next
        index += 1
        break
      case '--keep-remote-dir':
        options.keepRemoteDir = true
        break
      case '--json':
        options.json = true
        break
      case '--dry-run':
        options.dryRun = true
        break
      default:
        break
    }
  }

  return options
}

export function loadStagingProofManifest(filePath) {
  const absolutePath = path.resolve(filePath)
  const raw = fs.readFileSync(absolutePath, 'utf8')
  return {
    path: absolutePath,
    manifest: JSON.parse(raw),
  }
}

export function buildDefaultRemoteDir(gitRef = 'head') {
  const normalized = normalizeTrimmed(gitRef).replace(/[^a-zA-Z0-9._-]/g, '-')
  return `/tmp/hypertwist-website-staging-${normalized || 'head'}`
}

export function resolveStagingProofOptions({
  manifestPath,
  identityFile,
  vpsHost,
  vpsUser,
  archiveSource,
  remoteDir,
  port,
  waitSeconds,
  keepRemoteDir,
  json,
  dryRun,
  gitRef,
}) {
  return {
    manifestPath: path.resolve(requireNonEmpty(manifestPath, 'manifestPath')),
    identityFile: path.resolve(requireNonEmpty(identityFile, 'identityFile')),
    vpsHost: requireNonEmpty(vpsHost || '212.227.13.220', 'vpsHost'),
    vpsUser: requireNonEmpty(vpsUser || 'dev', 'vpsUser'),
    archiveSource: normalizeArchiveSource(archiveSource, 'git-head'),
    remoteDir: normalizeTrimmed(remoteDir) || buildDefaultRemoteDir(gitRef),
    port: requirePositiveInteger(port || '3011', 'port'),
    waitSeconds: requirePositiveInteger(waitSeconds || '20', 'waitSeconds'),
    keepRemoteDir: normalizeBoolean(keepRemoteDir, false),
    json: normalizeBoolean(json, false),
    dryRun: normalizeBoolean(dryRun, false),
  }
}

export function buildRemoteStagingManifest(manifest, {
  remoteDir,
  port,
}) {
  const server = manifest?.server && typeof manifest.server === 'object'
    ? manifest.server
    : {}

  return {
    ...manifest,
    checkoutRoot: remoteDir,
    server: {
      ...server,
      port: String(port),
      serveStaticWebsite: true,
      websiteDistPath: `${remoteDir}/website/dist`,
      billingStatePath: `${remoteDir}/hypertwist-billing-state.json`,
    },
  }
}

export function sanitizeStagingProofManifest(manifest) {
  return maskManifestSecrets(manifest)
}

export function buildRemoteStagingProofScript({
  remoteDir,
  waitSeconds,
  keepRemoteDir,
  manifest,
}) {
  const manifestJson = JSON.stringify(manifest, null, 2)
  const port = String(manifest.server?.port || '')

  return `set -euo pipefail
REMOTE_DIR=${JSON.stringify(remoteDir)}
WAIT_SECONDS=${Number(waitSeconds)}
KEEP_REMOTE_DIR=${keepRemoteDir ? '1' : '0'}
PORT=${JSON.stringify(port)}
WEBSITE_DIR="$REMOTE_DIR/website"
MANIFEST_PATH="$REMOTE_DIR/bundle.json"
BUNDLE_OUT="$REMOTE_DIR/rendered"
LOG_DIR="$REMOTE_DIR/logs"
SERVER_PID=""
PROOF_OK=0
cleanup() {
  LISTENER_PIDS=$(ss -ltnp 2>/dev/null | sed -n "s/.*:$PORT[^0-9].*pid=\\([0-9]\\+\\).*/\\1/p" | sort -u || true)
  if [ -n "$LISTENER_PIDS" ]; then
    for listener_pid in $LISTENER_PIDS; do
      kill "$listener_pid" 2>/dev/null || true
    done
  fi

  if [ -n "\${SERVER_PID:-}" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
    kill "$SERVER_PID" 2>/dev/null || true
    wait "$SERVER_PID" 2>/dev/null || true
  fi

  if [ "$KEEP_REMOTE_DIR" = "1" ] || [ "$PROOF_OK" != "1" ]; then
    return
  fi

  rm -rf "$REMOTE_DIR"
}
trap cleanup EXIT

mkdir -p "$REMOTE_DIR" "$LOG_DIR"
cd "$WEBSITE_DIR"

printf "[remote_dir] %s\\n" "$REMOTE_DIR"
printf "[proof_port] %s\\n" "$PORT"

cat > "$MANIFEST_PATH" <<'__HYPERTWIST_STAGING_MANIFEST__'
${manifestJson}
__HYPERTWIST_STAGING_MANIFEST__

npm ci > "$LOG_DIR/website-npm-ci.log" 2>&1
npm --prefix server ci > "$LOG_DIR/server-npm-ci.log" 2>&1
npm run render:same-origin-bundle -- --manifest "$MANIFEST_PATH" --out-dir "$BUNDLE_OUT" --summary > "$LOG_DIR/bundle-summary.log" 2>&1
cp "$BUNDLE_OUT/website.env" .env
cp "$BUNDLE_OUT/server.env" server/.env
npm run build > "$LOG_DIR/build.log" 2>&1
systemd-analyze verify "$BUNDLE_OUT/hypertwist-website-auth-server.service" > "$LOG_DIR/systemd-verify.log" 2>&1
npm --prefix server start > "$LOG_DIR/server.log" 2>&1 &
SERVER_PID=$!

for attempt in $(seq 1 "$WAIT_SECONDS"); do
  if curl --silent --show-error "http://127.0.0.1:$PORT/health" > "$LOG_DIR/health.json" 2> "$LOG_DIR/health.stderr"; then
    break
  fi
  sleep 1
done

if [ ! -s "$LOG_DIR/health.json" ]; then
  printf "Health endpoint never became ready on port %s.\\n" "$PORT" >&2
  sed -n '1,200p' "$LOG_DIR/server.log" >&2 || true
  exit 1
fi

curl --silent --show-error "http://127.0.0.1:$PORT/api/auth/health" > "$LOG_DIR/auth-health.json"
curl --silent --show-error "http://127.0.0.1:$PORT/api/releases/manifest" > "$LOG_DIR/release-manifest.json"
curl --silent --show-error "http://127.0.0.1:$PORT/" > "$LOG_DIR/root.html"
ss -ltnp | rg ":$PORT" > "$LOG_DIR/listener.txt" || true

node - "$REMOTE_DIR" <<'__HYPERTWIST_STAGING_SUMMARY__'
const fs = require('node:fs')
const path = require('node:path')

const remoteDir = process.argv[2]
const logDir = path.join(remoteDir, 'logs')

function readJson(fileName) {
  return JSON.parse(fs.readFileSync(path.join(logDir, fileName), 'utf8'))
}

function readText(fileName) {
  return fs.readFileSync(path.join(logDir, fileName), 'utf8')
}

const health = readJson('health.json')
const authHealth = readJson('auth-health.json')
const releaseManifest = readJson('release-manifest.json')
const rootHtml = readText('root.html')
const listenerOutput = fs.existsSync(path.join(logDir, 'listener.txt'))
  ? readText('listener.txt')
  : ''

const markerPresent = rootHtml.includes('hypertwist-site-shell')
const rootMountPresent = rootHtml.includes('id="root"')
const releasePlatforms = Array.isArray(releaseManifest?.manifest?.platforms)
  ? releaseManifest.manifest.platforms.map((platform) => ({
    platform_key: platform.platform_key,
    configured: Boolean(platform.configured),
    download_available: Boolean(platform.download_available),
  }))
  : []

const failures = []
if (!health?.ok) failures.push('/health did not return ok=true.')
if (!authHealth?.ok) failures.push('/api/auth/health did not return ok=true.')
if (!authHealth?.supertokens?.ready) failures.push('SuperTokens did not report ready=true.')
if (!authHealth?.runtime?.public_origin_ready) failures.push('Runtime did not report public_origin_ready=true.')
if (!releaseManifest?.ok) failures.push('/api/releases/manifest did not return ok=true.')
if (!markerPresent) failures.push('Root shell marker was not present in /.')
if (!rootMountPresent) failures.push('Root app mount was not present in /.')
if (!listenerOutput.trim()) failures.push('Expected listener on the staging port was not visible in ss output.')

const summary = {
  ok: failures.length === 0,
  remoteDir,
  health,
  authHealth: {
    ok: Boolean(authHealth?.ok),
    supertokens: authHealth?.supertokens || null,
    fallback: authHealth?.fallback || null,
    billing: authHealth?.billing || null,
    runtime: authHealth?.runtime || null,
  },
  releaseManifest: {
    ok: Boolean(releaseManifest?.ok),
    support_email: releaseManifest?.manifest?.support_email || null,
    viewer: releaseManifest?.manifest?.viewer || null,
    platforms: releasePlatforms,
  },
  rootShell: {
    markerPresent,
    rootMountPresent,
  },
  listenerOutput: listenerOutput.trim(),
  failures,
}

process.stdout.write(\`__HYPERTWIST_STAGING_SUMMARY__\${JSON.stringify(summary)}\\n\`)
if (!summary.ok) {
  process.exit(1)
}
__HYPERTWIST_STAGING_SUMMARY__

PROOF_OK=1
printf "[proof_result] ok\\n"
`
}

export function buildStageArchiveRemoteCommand(remoteDir) {
  return `rm -rf ${JSON.stringify(remoteDir)} && mkdir -p ${JSON.stringify(remoteDir)} && tar -xf - -C ${JSON.stringify(remoteDir)}`
}

export function buildSshArgs({
  identityFile,
  vpsUser,
  vpsHost,
  remoteCommand,
}) {
  const args = [
    '-i',
    identityFile,
    '-o',
    'StrictHostKeyChecking=no',
    `${vpsUser}@${vpsHost}`,
  ]

  if (remoteCommand) {
    args.push(remoteCommand)
  }

  return args
}

export function extractStagingProofSummary(output) {
  const marker = '__HYPERTWIST_STAGING_SUMMARY__'
  const lines = String(output || '').split('\n')
  const summaryLine = [...lines].reverse().find((line) => line.startsWith(marker))
  if (!summaryLine) {
    return null
  }

  return JSON.parse(summaryLine.slice(marker.length))
}
