import { execSync, spawn } from 'node:child_process'
import { existsSync } 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_LAUNCHER_EXE = 'FarmControl.exe'
const WINDOWS_LEGACY_LAUNCHER_EXE = 'launcher.exe'
const WINDOWS_DEFAULT_BIN_DIR = path.join(
  process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
  'Programs',
  'Farm Control',
  'bin'
)

const resolveExistingLauncher = (...candidates) =>
  candidates.find((candidate) => existsSync(candidate)) || null

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

export 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') {
    // Prefer the installed launcher so restarts target the registered install dir.
    const installDir = readWindowsInstallDirFromRegistry()
    if (installDir) {
      const launcherFromRegistry = resolveExistingLauncher(
        path.join(installDir, 'bin', WINDOWS_LAUNCHER_EXE),
        path.join(installDir, 'bin', WINDOWS_LEGACY_LAUNCHER_EXE)
      )
      if (launcherFromRegistry) {
        return launcherFromRegistry
      }
    }

    const defaultLauncher = resolveExistingLauncher(
      path.join(WINDOWS_DEFAULT_BIN_DIR, WINDOWS_LAUNCHER_EXE),
      path.join(WINDOWS_DEFAULT_BIN_DIR, WINDOWS_LEGACY_LAUNCHER_EXE)
    )
    if (defaultLauncher) {
      return defaultLauncher
    }

    const execBase = path.basename(process.execPath).toLowerCase()
    if (
      execBase === WINDOWS_LAUNCHER_EXE.toLowerCase() ||
      execBase === WINDOWS_LEGACY_LAUNCHER_EXE
    ) {
      return process.execPath
    }

    const launcherBesideExec = resolveExistingLauncher(
      path.join(path.dirname(process.execPath), WINDOWS_LAUNCHER_EXE),
      path.join(path.dirname(process.execPath), WINDOWS_LEGACY_LAUNCHER_EXE)
    )
    if (launcherBesideExec) {
      return launcherBesideExec
    }

    const launcherInBin = resolveExistingLauncher(
      path.join(path.dirname(process.execPath), 'bin', WINDOWS_LAUNCHER_EXE),
      path.join(path.dirname(process.execPath), 'bin', WINDOWS_LEGACY_LAUNCHER_EXE)
    )
    if (launcherInBin) {
      return launcherInBin
    }

    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()
}

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

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

  throw new Error(`App restart scheduling is not supported on ${process.platform}.`)
}
