import { execSync, spawn } from 'node:child_process'
import { existsSync } from 'node:fs'
import { unlink, writeFile } from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import process from 'node:process'
import sudo from '@vscode/sudo-prompt'
import { findMacAppBundle } from './updater-runner.js'

const MAC_APP_NAME = 'Farm Control.app'
const MAC_SYSTEM_APP_PATH = path.join('/Applications', MAC_APP_NAME)
const MAC_PKG_IDENTIFIER = 'com.tombutcher.farmcontrol'

const WINDOWS_APP_DIR_NAME = 'Farm Control'
const WINDOWS_LAUNCHER_EXE = 'FarmControl.exe'
const WINDOWS_LEGACY_LAUNCHER_EXE = 'launcher.exe'
const WINDOWS_REGISTRY_APP_KEY = 'Software\\Tom Butcher\\Farm Control'
const WINDOWS_REGISTRY_UNINSTALL_KEY =
  'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Farm Control'

const UNSUPPORTED_RESULT = {
  supported: false,
  runningInstall: 'unknown',
  runningPath: null,
  duplicatePath: null,
}

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

const isPathInside = (childPath, parentPath) => {
  if (!childPath || !parentPath) return false
  const relative = path.relative(parentPath, childPath)
  return (
    relative === '' ||
    (!relative.startsWith('..') && !path.isAbsolute(relative))
  )
}

const samePath = (left, right) => {
  if (!left || !right) return false
  if (process.platform === 'win32') {
    return path.resolve(left).toLowerCase() === path.resolve(right).toLowerCase()
  }
  return path.resolve(left) === path.resolve(right)
}

const checkMacInstallations = () => {
  const runningBundle = findMacAppBundle(process.execPath)
  const userAppPath = path.join(os.homedir(), 'Applications', MAC_APP_NAME)

  let runningInstall = 'unknown'
  if (runningBundle) {
    if (isPathInside(runningBundle, path.join(os.homedir(), 'Applications'))) {
      runningInstall = 'user'
    } else if (isPathInside(runningBundle, '/Applications')) {
      runningInstall = 'system'
    }
  }

  const systemCopyExists =
    existsSync(MAC_SYSTEM_APP_PATH) &&
    !samePath(runningBundle, MAC_SYSTEM_APP_PATH)

  return {
    supported: true,
    runningInstall,
    runningPath: runningBundle,
    userPath: userAppPath,
    duplicatePath:
      runningInstall === 'user' && systemCopyExists
        ? MAC_SYSTEM_APP_PATH
        : null,
  }
}

const readWindowsInstallDir = (hive) => {
  try {
    const output = execSync(
      `reg query "${hive}\\${WINDOWS_REGISTRY_APP_KEY}" /v InstallDir`,
      { encoding: 'utf8', windowsHide: true },
    )
    const match = output.match(/InstallDir\s+REG_\w+\s+(.+)/i)
    return match?.[1]?.trim() || null
  } catch {
    return null
  }
}

const windowsDirLooksLikeInstall = (dir) =>
  Boolean(dir) &&
  (existsSync(path.join(dir, 'bin', WINDOWS_LAUNCHER_EXE)) ||
    existsSync(path.join(dir, 'bin', WINDOWS_LEGACY_LAUNCHER_EXE)) ||
    existsSync(path.join(dir, WINDOWS_LAUNCHER_EXE)) ||
    existsSync(path.join(dir, 'Uninstall.exe')) ||
    existsSync(path.join(dir, `Uninstall ${WINDOWS_APP_DIR_NAME}.exe`)))

const getWindowsUserInstallDir = () =>
  readWindowsInstallDir('HKCU') ||
  path.join(
    process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
    'Programs',
    WINDOWS_APP_DIR_NAME,
  )

const getWindowsSystemInstallCandidates = () => {
  const candidates = [
    readWindowsInstallDir('HKLM'),
    process.env.ProgramFiles &&
      path.join(process.env.ProgramFiles, WINDOWS_APP_DIR_NAME),
    process.env['ProgramFiles(x86)'] &&
      path.join(process.env['ProgramFiles(x86)'], WINDOWS_APP_DIR_NAME),
    process.env.ProgramW6432 &&
      path.join(process.env.ProgramW6432, WINDOWS_APP_DIR_NAME),
  ].filter(Boolean)

  const unique = []
  for (const candidate of candidates) {
    if (!unique.some((existing) => samePath(existing, candidate))) {
      unique.push(candidate)
    }
  }
  return unique
}

const checkWindowsInstallations = () => {
  const runningDir = path.dirname(process.execPath)
  const userInstallDir = getWindowsUserInstallDir()
  const systemCandidates = getWindowsSystemInstallCandidates()
  const localAppData =
    process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local')

  let runningInstall = 'unknown'
  if (
    isPathInside(runningDir, userInstallDir) ||
    isPathInside(runningDir, localAppData)
  ) {
    runningInstall = 'user'
  } else if (
    systemCandidates.some((candidate) => isPathInside(runningDir, candidate))
  ) {
    runningInstall = 'system'
  }

  const duplicatePath =
    runningInstall === 'user'
      ? systemCandidates.find(
          (candidate) =>
            windowsDirLooksLikeInstall(candidate) &&
            !isPathInside(runningDir, candidate),
        ) || null
      : null

  return {
    supported: true,
    runningInstall,
    runningPath: runningDir,
    userPath: userInstallDir,
    duplicatePath,
  }
}

export const checkForDuplicateInstallations = () => {
  try {
    if (process.platform === 'darwin') return checkMacInstallations()
    if (process.platform === 'win32') return checkWindowsInstallations()
  } catch (error) {
    console.warn(
      '[duplicate-install] Failed to check for duplicate installations.',
      error,
    )
  }
  return UNSUPPORTED_RESULT
}

const removeMacDuplicate = (duplicatePath) =>
  new Promise((resolve, reject) => {
    // Removing from /Applications typically needs admin rights; also forget
    // the pkg receipt so future installer runs start clean.
    const script = [
      `/bin/rm -rf ${quoteShellArg(duplicatePath)}`,
      `/usr/sbin/pkgutil --forget ${quoteShellArg(MAC_PKG_IDENTIFIER)} || true`,
    ].join(' && ')

    sudo.exec(script, { name: 'farmcontrol' }, (error, stdout, stderr) => {
      if (error) {
        const combined = `${stderr || ''}\n${error?.message || ''}`
        const message =
          /cancel/i.test(combined) || /did not grant permission/i.test(combined)
            ? 'Removal was cancelled.'
            : 'Failed to remove the duplicate installation.'
        reject(new Error(message))
        return
      }
      resolve()
    })
  })

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

// Intentionally does NOT run the old install's Uninstall.exe: it taskkills
// FarmControl.exe, which would terminate the running app. Instead the old
// files, HKLM registry keys and machine-level shortcuts are removed directly.
const buildWindowsRemovalScript = (duplicatePath) =>
  [
    `$ErrorActionPreference = 'SilentlyContinue'`,
    `$installDir = ${psQuote(duplicatePath)}`,
    `if (Test-Path -LiteralPath $installDir) {`,
    `  Remove-Item -LiteralPath $installDir -Recurse -Force`,
    `}`,
    `& reg.exe delete ${psQuote(`HKLM\\${WINDOWS_REGISTRY_APP_KEY}`)} /f 2>$null | Out-Null`,
    `& reg.exe delete ${psQuote(`HKLM\\${WINDOWS_REGISTRY_UNINSTALL_KEY}`)} /f 2>$null | Out-Null`,
    `$commonStartMenu = Join-Path $env:ProgramData 'Microsoft\\Windows\\Start Menu\\Programs\\${WINDOWS_APP_DIR_NAME}'`,
    `if (Test-Path -LiteralPath $commonStartMenu) {`,
    `  Remove-Item -LiteralPath $commonStartMenu -Recurse -Force`,
    `}`,
    `$publicDesktopShortcut = 'C:\\Users\\Public\\Desktop\\${WINDOWS_APP_DIR_NAME}.lnk'`,
    `if (Test-Path -LiteralPath $publicDesktopShortcut) {`,
    `  Remove-Item -LiteralPath $publicDesktopShortcut -Force`,
    `}`,
    `if (Test-Path -LiteralPath $installDir) { exit 1 }`,
    `exit 0`,
  ].join('\r\n')

const removeWindowsDuplicate = async (duplicatePath) => {
  const scriptPath = path.join(
    os.tmpdir(),
    `farmcontrol-remove-duplicate-${Date.now()}.ps1`,
  )
  await writeFile(scriptPath, buildWindowsRemovalScript(duplicatePath), 'utf8')

  // Pre-quote the -File argument: PowerShell 5.1's Start-Process does not
  // quote ArgumentList entries containing spaces.
  const elevateCommand = [
    `$p = Start-Process -FilePath 'powershell.exe'`,
    `-ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File','"${scriptPath.replaceAll("'", "''")}"')`,
    `-Verb RunAs -Wait -PassThru; exit $p.ExitCode`,
  ].join(' ')

  try {
    await new Promise((resolve, reject) => {
      const child = spawn(
        'powershell.exe',
        ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', elevateCommand],
        { stdio: 'ignore', windowsHide: true },
      )

      child.on('error', reject)
      child.on('exit', (code) => {
        if (code === 0) {
          resolve()
          return
        }
        // Start-Process throws when the UAC prompt is declined, which exits
        // the outer PowerShell with a non-zero code and no script output.
        reject(
          new Error(
            code === 1
              ? 'Failed to remove the duplicate installation.'
              : 'Removal was cancelled.',
          ),
        )
      })
    })
  } finally {
    await unlink(scriptPath).catch(() => {})
  }
}

export const removeDuplicateInstallations = async () => {
  // Re-detect instead of trusting a path from the renderer so this can never
  // be used to delete an arbitrary directory.
  const check = checkForDuplicateInstallations()

  if (!check.duplicatePath) {
    return { ok: false, error: 'No duplicate installation was found.' }
  }

  try {
    if (process.platform === 'darwin') {
      await removeMacDuplicate(check.duplicatePath)
    } else if (process.platform === 'win32') {
      await removeWindowsDuplicate(check.duplicatePath)
    } else {
      return { ok: false, error: 'Not supported on this platform.' }
    }
  } catch (error) {
    return {
      ok: false,
      error: error?.message || 'Failed to remove the duplicate installation.',
    }
  }

  return { ok: true, removedPath: check.duplicatePath }
}
