#!/usr/bin/env node

import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { execFileSync, spawnSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'

import {
  buildRuntimeReadinessReport,
  fetchLiveAuthHealth,
  fetchLiveReleaseManifest,
  fetchLiveWebsiteShell,
  formatRuntimeReadinessReport,
  parseEnvFile,
} from './runtime-readiness-lib.mjs'
import { writeSameOriginBundle } from './render-same-origin-bundle-lib.mjs'
import {
  buildDefaultLiveDeployBundleDir,
  buildRemoteLiveDeployScript,
  buildSshArgs,
  buildStageBundleRemoteCommand,
  buildStageCheckoutRemoteCommand,
  extractLiveDeploySummary,
  loadLiveDeployManifest,
  parseLiveDeployArgs,
  resolveLiveDeployOptions,
  sanitizeLiveDeployManifest,
} from './run-vps-same-origin-live-deploy-lib.mjs'

const currentFile = fileURLToPath(import.meta.url)
const websiteRoot = path.resolve(path.dirname(currentFile), '..')
const repoRoot = path.resolve(websiteRoot, '..')

function runCommand(command, args, options = {}) {
  const result = spawnSync(command, args, {
    encoding: 'utf8',
    maxBuffer: 64 * 1024 * 1024,
    ...options,
  })

  if (result.error) {
    throw result.error
  }

  return result
}

function buildArchiveBuffer(repoRootPath, archiveSource) {
  if (archiveSource === 'worktree') {
    return execFileSync('tar', [
      '--exclude=website/node_modules',
      '--exclude=website/dist',
      '--exclude=website/coverage',
      '--exclude=website/server/node_modules',
      '--exclude=website/server/dist',
      '-cf',
      '-',
      'website',
    ], {
      cwd: repoRootPath,
      encoding: null,
      maxBuffer: 64 * 1024 * 1024,
    })
  }

  return execFileSync('git', ['archive', '--format=tar', 'HEAD', 'website'], {
    cwd: repoRootPath,
    encoding: null,
    maxBuffer: 64 * 1024 * 1024,
  })
}

function buildRenderedBundleArchive(renderedDir) {
  return execFileSync('tar', ['-cf', '-', '-C', renderedDir, '.'], {
    encoding: null,
    maxBuffer: 64 * 1024 * 1024,
  })
}

function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms)
  })
}

async function waitForLiveAuthHealth(baseUrl, waitSeconds) {
  let lastError = null

  for (let attempt = 1; attempt <= waitSeconds; attempt += 1) {
    try {
      return await fetchLiveAuthHealth(baseUrl)
    } catch (error) {
      lastError = error
      if (attempt < waitSeconds) {
        await sleep(1000)
      }
    }
  }

  throw lastError || new Error(`Live auth health did not become ready within ${waitSeconds} seconds.`)
}

async function main() {
  const rawArgs = parseLiveDeployArgs(process.argv.slice(2))
  const gitRef = execFileSync('git', ['rev-parse', '--short', 'HEAD'], {
    cwd: repoRoot,
    encoding: 'utf8',
  }).trim()

  const { path: manifestPath, manifest } = loadLiveDeployManifest(rawArgs.manifest)
  const options = resolveLiveDeployOptions({
    manifestPath,
    identityFile: rawArgs.identityFile,
    vpsHost: rawArgs.vpsHost,
    vpsUser: rawArgs.vpsUser,
    archiveSource: rawArgs.archiveSource,
    bundleDir: rawArgs.bundleDir,
    healthUrl: rawArgs.healthUrl,
    waitSeconds: rawArgs.waitSeconds,
    keepBundleDir: rawArgs.keepBundleDir,
    skipLiveValidation: rawArgs.skipLiveValidation,
    json: rawArgs.json,
    dryRun: rawArgs.dryRun,
    manifest,
    gitRef,
  })

  const deployScript = buildRemoteLiveDeployScript({
    checkoutRoot: options.checkoutRoot,
    bundleDir: options.bundleDir,
    keepBundleDir: options.keepBundleDir,
    manifest,
  })
  const checkoutStageCommand = buildStageCheckoutRemoteCommand(options.checkoutRoot)
  const bundleStageCommand = buildStageBundleRemoteCommand(options.bundleDir)
  const sanitizedManifest = sanitizeLiveDeployManifest(manifest)

  if (options.dryRun) {
    const payload = {
      repoRoot,
      websiteRoot,
      manifestPath,
      vpsHost: options.vpsHost,
      vpsUser: options.vpsUser,
      archiveSource: options.archiveSource,
      checkoutRoot: options.checkoutRoot,
      bundleDir: options.bundleDir,
      healthUrl: options.healthUrl,
      waitSeconds: options.waitSeconds,
      keepBundleDir: options.keepBundleDir,
      skipLiveValidation: options.skipLiveValidation,
      checkoutStageCommand,
      bundleStageCommand,
      manifest: sanitizedManifest,
      deployScript,
      defaultBundleDirForGitRef: buildDefaultLiveDeployBundleDir(gitRef),
    }

    if (options.json) {
      process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`)
    } else {
      process.stdout.write(`[live-deploy] manifest: ${manifestPath}\n`)
      process.stdout.write(`[live-deploy] vps: ${options.vpsUser}@${options.vpsHost}\n`)
      process.stdout.write(`[live-deploy] archive source: ${options.archiveSource}\n`)
      process.stdout.write(`[live-deploy] checkout root: ${options.checkoutRoot}\n`)
      process.stdout.write(`[live-deploy] bundle dir: ${options.bundleDir}\n`)
      process.stdout.write(`[live-deploy] health url: ${options.healthUrl}\n`)
    }
    return
  }

  const localBundleDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hypertwist-live-deploy-'))

  try {
    const rendered = writeSameOriginBundle({
      manifest,
      outDir: localBundleDir,
    })
    const frontendEnv = parseEnvFile(rendered.frontendEnvContent)
    const serverEnv = parseEnvFile(rendered.serverEnvContent)
    const checkoutArchiveBuffer = buildArchiveBuffer(repoRoot, options.archiveSource)
    const bundleArchiveBuffer = buildRenderedBundleArchive(localBundleDir)

    const checkoutStageResult = runCommand('ssh', buildSshArgs({
      identityFile: options.identityFile,
      vpsUser: options.vpsUser,
      vpsHost: options.vpsHost,
      remoteCommand: checkoutStageCommand,
    }), {
      input: checkoutArchiveBuffer,
      encoding: 'utf8',
    })

    if (checkoutStageResult.status !== 0) {
      process.stderr.write(checkoutStageResult.stderr || '')
      process.exit(checkoutStageResult.status || 1)
    }

    const bundleStageResult = runCommand('ssh', buildSshArgs({
      identityFile: options.identityFile,
      vpsUser: options.vpsUser,
      vpsHost: options.vpsHost,
      remoteCommand: bundleStageCommand,
    }), {
      input: bundleArchiveBuffer,
      encoding: 'utf8',
    })

    if (bundleStageResult.status !== 0) {
      process.stderr.write(bundleStageResult.stderr || '')
      process.exit(bundleStageResult.status || 1)
    }

    const deployResult = runCommand('ssh', [
      ...buildSshArgs({
        identityFile: options.identityFile,
        vpsUser: options.vpsUser,
        vpsHost: options.vpsHost,
      }),
      'bash',
      '-s',
    ], {
      input: deployScript,
    })

    process.stderr.write(deployResult.stderr || '')
    const deploySummary = extractLiveDeploySummary(deployResult.stdout || '')

    let liveHealth = null
    let liveReleaseManifest = null
    let liveWebsiteShell = null
    const attemptedLiveValidation = !options.skipLiveValidation

    if (!options.skipLiveValidation) {
      liveHealth = await waitForLiveAuthHealth(options.healthUrl, options.waitSeconds)
      liveReleaseManifest = await fetchLiveReleaseManifest(options.healthUrl)
      liveWebsiteShell = await fetchLiveWebsiteShell(options.healthUrl)
    }

    const readinessReport = buildRuntimeReadinessReport({
      frontendEnv,
      serverEnv,
      liveHealth,
      liveReleaseManifest,
      liveWebsiteShell,
      liveHealthAttempted: attemptedLiveValidation,
      liveReleaseManifestAttempted: attemptedLiveValidation,
      liveWebsiteShellAttempted: attemptedLiveValidation,
      frontendEnvPath: path.join(localBundleDir, 'website.env'),
      serverEnvPath: path.join(localBundleDir, 'server.env'),
      healthBaseUrl: options.healthUrl,
      deploymentTier: manifest.deploymentTier || manifest?.server?.deploymentTier || '',
    })

    const payload = {
      ok: deployResult.status === 0 && readinessReport.ok,
      manifestPath,
      vpsHost: options.vpsHost,
      vpsUser: options.vpsUser,
      archiveSource: options.archiveSource,
      checkoutRoot: options.checkoutRoot,
      bundleDir: options.bundleDir,
      healthUrl: options.healthUrl,
      deploySummary,
      readinessReport,
      liveHealth,
      liveReleaseManifest,
      liveWebsiteShell,
    }

    if (options.json) {
      process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`)
    } else {
      if (deploySummary) {
        process.stdout.write(`[live-deploy] service status: ${deploySummary.serviceStatus}\n`)
        process.stdout.write(`[live-deploy] nginx status: ${deploySummary.nginxStatus}\n`)
      }
      process.stdout.write(`${formatRuntimeReadinessReport(readinessReport)}\n`)
    }

    if (deployResult.status !== 0 || !readinessReport.ok) {
      process.exit(deployResult.status || 1)
    }
  } finally {
    fs.rmSync(localBundleDir, { recursive: true, force: true })
  }
}

main().catch((error) => {
  process.stderr.write(`${error.stack || error.message}\n`)
  process.exit(1)
})
