import { execSync, spawn } from 'node:child_process'
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import process from 'node:process'

const MAC_APP_NAME = 'Farm Control.app'
const WINDOWS_INSTALL_DIR_KEYS = [
  'HKCU\\Software\\Tom Butcher\\Farm Control',
  'HKLM\\Software\\Tom Butcher\\Farm Control'
]
const WINDOWS_DEFAULT_LAUNCHER = path.join(
  process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
  'Programs',
  'Farm Control',
  'bin',
  'launcher.exe'
)

const quoteBatchArg = (value) => `"${String(value).replaceAll('"', '""')}"`

const findMacAppBundle = (startPath) => {
  let current = path.resolve(startPath)

  while (current !== path.dirname(current)) {
    if (current.endsWith('.app')) {
      return current
    }

    const baseName = path.basename(current)
    if (baseName === 'Contents') {
      const bundlePath = path.dirname(current)
      if (bundlePath.endsWith('.app')) {
        return bundlePath
      }
    }

    current = path.dirname(current)
  }

  return null
}

const readWindowsInstallDirFromRegistry = () => {
  for (const registryKey of WINDOWS_INSTALL_DIR_KEYS) {
    try {
      const output = execSync(
        `reg query ${quoteBatchArg(registryKey)} /v InstallDir`,
        { encoding: 'utf8', windowsHide: true }
      )
      const match = output.match(/InstallDir\s+REG_\w+\s+(.+)/i)
      const installDir = match?.[1]?.trim()
      if (installDir) {
        return installDir
      }
    } catch {
      // Try the next registry key.
    }
  }

  return null
}

export const resolveAppLaunchPath = () => {
  if (process.platform === 'darwin') {
    const bundleFromExec = findMacAppBundle(process.execPath)
    if (bundleFromExec) {
      return bundleFromExec
    }

    const applicationsPath = path.join('/Applications', MAC_APP_NAME)
    if (existsSync(applicationsPath)) {
      return applicationsPath
    }

    return process.execPath
  }

  if (process.platform === 'win32') {
    const execBase = path.basename(process.execPath).toLowerCase()
    if (execBase === 'launcher.exe') {
      return process.execPath
    }

    const launcherBesideExec = path.join(path.dirname(process.execPath), 'launcher.exe')
    if (existsSync(launcherBesideExec)) {
      return launcherBesideExec
    }

    const launcherInBin = path.join(
      path.dirname(process.execPath),
      'bin',
      'launcher.exe'
    )
    if (existsSync(launcherInBin)) {
      return launcherInBin
    }

    const installDir = readWindowsInstallDirFromRegistry()
    if (installDir) {
      const launcherFromRegistry = path.join(installDir, 'bin', 'launcher.exe')
      if (existsSync(launcherFromRegistry)) {
        return launcherFromRegistry
      }
    }

    if (existsSync(WINDOWS_DEFAULT_LAUNCHER)) {
      return WINDOWS_DEFAULT_LAUNCHER
    }

    return process.execPath
  }

  throw new Error(`App updates are not supported on ${process.platform}.`)
}

const spawnDetachedMacRestart = ({ appLaunchPath, parentPid }) => {
  const child = spawn(
    '/bin/sh',
    [
      '-c',
      `while kill -0 ${parentPid} 2>/dev/null; do sleep 0.5; done; sleep 1; /usr/bin/open ${JSON.stringify(appLaunchPath)}`
    ],
    {
      detached: true,
      stdio: 'ignore'
    }
  )
  child.unref()
}

const spawnDetachedWindowsRestart = ({ appLaunchPath, parentPid }) => {
  const updateDir = path.join(
    os.homedir(),
    'AppData',
    'Local',
    'FarmControl',
    'Updates'
  )
  const scriptPath = path.join(updateDir, `restart-${parentPid}.bat`)
  const appLaunchWin = appLaunchPath.replaceAll('/', '\\')
  const appDirWin = path.dirname(appLaunchWin)

  const script = `@echo off
setlocal

set "PARENT_PID=${parentPid}"
set "APP_LAUNCHER=${appLaunchWin}"
set "APP_DIR=${appDirWin}"

:waitparent
tasklist /FI "PID eq %PARENT_PID%" 2>NUL | find "%PARENT_PID%" >NUL && (
  timeout /t 1 /nobreak >nul
  goto waitparent
)

timeout /t 2 /nobreak >nul
cd /d "%APP_DIR%"
start "" "%APP_LAUNCHER%"

ping -n 2 127.0.0.1 >nul
del "%~f0"
`

  mkdirSync(updateDir, { recursive: true })
  writeFileSync(scriptPath, script, 'utf8')

  const child = spawn('cmd.exe', ['/c', scriptPath], {
    detached: true,
    stdio: 'ignore',
    windowsHide: true
  })
  child.unref()
}

export const scheduleAppRestart = ({ parentPid = process.pid } = {}) => {
  const appLaunchPath = resolveAppLaunchPath()

  if (process.platform === 'darwin') {
    spawnDetachedMacRestart({ appLaunchPath, parentPid })
    return { appLaunchPath }
  }

  if (process.platform === 'win32') {
    spawnDetachedWindowsRestart({ appLaunchPath, parentPid })
    return { appLaunchPath }
  }

  throw new Error(`App updates are not supported on ${process.platform}.`)
}
