#!/usr/bin/env node
/**
 * Tiny HTTP agent on the dashboards VPS.
 * Super Admin → Nest API → POST here → runs ssl-sync.sh
 *
 * Env (/etc/meshkee/ssl-sync-agent.env):
 *   SSL_SYNC_AGENT_TOKEN=...
 *   SSL_SYNC_SCRIPT=/opt/meshkee/dashboards/deploy/ssl-sync.sh
 *   PORT=9051
 *   BIND=0.0.0.0
 *
 * POST /ssl-sync
 *   Body (optional JSON): { "wait": true }
 *   wait=false (default): accept and run in background → 202
 *   wait=true: run script and wait for exit → 200 / 500
 */
import { createServer } from 'node:http'
import { spawn } from 'node:child_process'
import { accessSync, constants } from 'node:fs'

const TOKEN = (process.env.SSL_SYNC_AGENT_TOKEN || '').trim()
const SCRIPT =
  (process.env.SSL_SYNC_SCRIPT || '/opt/meshkee/dashboards/deploy/ssl-sync.sh').trim()
const PORT = Number(process.env.PORT || 9051)
const BIND = (process.env.BIND || '0.0.0.0').trim()

if (!TOKEN) {
  console.error('SSL_SYNC_AGENT_TOKEN is required')
  process.exit(1)
}

try {
  accessSync(SCRIPT, constants.X_OK)
} catch {
  console.error(`SSL sync script missing or not executable: ${SCRIPT}`)
  process.exit(1)
}

let running = false

function json(res, status, body) {
  const payload = JSON.stringify(body)
  res.writeHead(status, {
    'Content-Type': 'application/json',
    'Content-Length': Buffer.byteLength(payload),
  })
  res.end(payload)
}

function readJson(req) {
  return new Promise((resolve, reject) => {
    const chunks = []
    req.on('data', (c) => chunks.push(c))
    req.on('end', () => {
      const raw = Buffer.concat(chunks).toString('utf8').trim()
      if (!raw) return resolve({})
      try {
        resolve(JSON.parse(raw))
      } catch (err) {
        reject(err)
      }
    })
    req.on('error', reject)
  })
}

function startSyncDetached() {
  running = true
  const child = spawn(SCRIPT, [], {
    detached: true,
    stdio: 'ignore',
    env: process.env,
  })
  child.on('error', (err) => {
    console.error(`${new Date().toISOString()} spawn error:`, err.message)
    running = false
  })
  child.on('exit', (code, signal) => {
    console.log(
      `${new Date().toISOString()} ssl-sync finished code=${code} signal=${signal ?? ''}`,
    )
    running = false
  })
  child.unref()
}

function runSyncAndWait() {
  return new Promise((resolve) => {
    running = true
    const child = spawn(SCRIPT, [], {
      stdio: ['ignore', 'pipe', 'pipe'],
      env: process.env,
    })
    let stdout = ''
    let stderr = ''
    child.stdout.on('data', (d) => {
      stdout += d.toString()
    })
    child.stderr.on('data', (d) => {
      stderr += d.toString()
    })
    child.on('error', (err) => {
      running = false
      resolve({
        ok: false,
        code: 1,
        log: err.message,
      })
    })
    child.on('exit', (code, signal) => {
      running = false
      const log = `${stdout}${stderr}`.trim().slice(-4000)
      console.log(
        `${new Date().toISOString()} ssl-sync waited code=${code} signal=${signal ?? ''}`,
      )
      resolve({
        ok: code === 0,
        code: code ?? 1,
        log,
      })
    })
  })
}

const server = createServer(async (req, res) => {
  if (req.method === 'GET' && req.url === '/health') {
    return json(res, 200, { ok: true, running })
  }

  if (req.method !== 'POST' || req.url !== '/ssl-sync') {
    return json(res, 404, { error: 'not found' })
  }

  const provided = String(req.headers['x-ssl-sync-agent-token'] ?? '').trim()
  if (!provided || provided !== TOKEN) {
    return json(res, 401, { error: 'unauthorized' })
  }

  if (running) {
    return json(res, 409, { error: 'ssl sync already running' })
  }

  let body = {}
  try {
    body = await readJson(req)
  } catch {
    return json(res, 400, { error: 'invalid json' })
  }

  const wait = body && body.wait === true

  if (wait) {
    console.log(`${new Date().toISOString()} ssl-sync wait start`)
    const result = await runSyncAndWait()
    if (!result.ok) {
      return json(res, 500, {
        status: 'failed',
        message: 'SSL sync script failed',
        code: result.code,
        log: result.log,
      })
    }
    return json(res, 200, {
      status: 'ok',
      message: 'SSL sync completed',
      log: result.log,
    })
  }

  startSyncDetached()
  console.log(`${new Date().toISOString()} ssl-sync accepted`)
  return json(res, 202, {
    status: 'accepted',
    message: 'SSL sync started',
  })
})

server.listen(PORT, BIND, () => {
  console.log(`ssl-sync-agent listening on ${BIND}:${PORT}`)
})
