/**
 * OpenClaw CLI utilities — cross-platform auto-install
 */
import { app } from 'electron';
import {
  appendFileSync,
  chmodSync,
  existsSync,
  mkdirSync,
  readFileSync,
  symlinkSync,
  unlinkSync,
} from 'node:fs';
import { spawn } from 'node:child_process';
import { homedir } from 'node:os';
import { join, dirname } from 'node:path';
import { getOpenClawDir, getOpenClawEntryPath } from './paths';
import { logger } from './logger';

// ── Quoting helpers ──────────────────────────────────────────────────────────

function escapeForDoubleQuotes(value: string): string {
  return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
}

function quoteForPosix(value: string): string {
  return `"${escapeForDoubleQuotes(value)}"`;
}

function quoteForPowerShell(value: string): string {
  return `'${value.replace(/'/g, "''")}'`;
}

// ── CLI command string (for display / copy) ──────────────────────────────────

export function getOpenClawCliCommand(): string {
  const entryPath = getOpenClawEntryPath();
  const platform = process.platform;

  if (platform === 'darwin' || platform === 'linux') {
    const localBinPath = join(homedir(), '.local', 'bin', 'openclaw');
    if (existsSync(localBinPath)) {
      return quoteForPosix(localBinPath);
    }
  }

  if (platform === 'linux') {
    if (existsSync('/usr/local/bin/openclaw')) {
      return '/usr/local/bin/openclaw';
    }
  }

  if (!app.isPackaged) {
    const openclawDir = getOpenClawDir();
    const nodeModulesDir = dirname(openclawDir);
    const binName = platform === 'win32' ? 'openclaw.cmd' : 'openclaw';
    const binPath = join(nodeModulesDir, '.bin', binName);

    if (existsSync(binPath)) {
      if (platform === 'win32') {
        return `& ${quoteForPowerShell(binPath)}`;
      }
      return quoteForPosix(binPath);
    }
  }

  if (app.isPackaged) {
    if (platform === 'win32') {
      const cliDir = join(process.resourcesPath, 'cli');
      const cmdPath = join(cliDir, 'openclaw.cmd');
      if (existsSync(cmdPath)) {
        return quoteForPowerShell(cmdPath);
      }
    }

    const execPath = process.execPath;
    if (platform === 'win32') {
      return `$env:ELECTRON_RUN_AS_NODE=1; & ${quoteForPowerShell(execPath)} ${quoteForPowerShell(entryPath)}`;
    }
    return `ELECTRON_RUN_AS_NODE=1 ${quoteForPosix(execPath)} ${quoteForPosix(entryPath)}`;
  }

  if (platform === 'win32') {
    return `node ${quoteForPowerShell(entryPath)}`;
  }

  return `node ${quoteForPosix(entryPath)}`;
}

// ── Packaged CLI wrapper path ────────────────────────────────────────────────

function getPackagedCliWrapperPath(): string | null {
  if (!app.isPackaged) return null;
  const platform = process.platform;

  if (platform === 'darwin' || platform === 'linux') {
    const wrapper = join(process.resourcesPath, 'cli', 'openclaw');
    return existsSync(wrapper) ? wrapper : null;
  }
  if (platform === 'win32') {
    const wrapper = join(process.resourcesPath, 'cli', 'openclaw.cmd');
    return existsSync(wrapper) ? wrapper : null;
  }
  return null;
}

function getWindowsPowerShellPath(): string {
  const systemRoot = process.env.SystemRoot || 'C:\\Windows';
  return join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
}

// ── macOS / Linux install ────────────────────────────────────────────────────

function getCliTargetPath(): string {
  return join(homedir(), '.local', 'bin', 'openclaw');
}

export async function installOpenClawCli(): Promise<{
  success: boolean; path?: string; error?: string;
}> {
  const platform = process.platform;

  if (platform === 'win32') {
    return { success: false, error: 'Windows CLI is configured by the installer.' };
  }

  if (!app.isPackaged) {
    return { success: false, error: 'CLI install is only available in packaged builds.' };
  }

  const wrapperSrc = getPackagedCliWrapperPath();
  if (!wrapperSrc) {
    return { success: false, error: 'CLI wrapper not found in app resources.' };
  }

  const targetDir = join(homedir(), '.local', 'bin');
  const target = getCliTargetPath();

  try {
    mkdirSync(targetDir, { recursive: true });

    // Remove existing file/symlink to avoid EEXIST
    if (existsSync(target)) {
      unlinkSync(target);
    }

    symlinkSync(wrapperSrc, target);
    chmodSync(wrapperSrc, 0o755);
    logger.info(`OpenClaw CLI symlink created: ${target} -> ${wrapperSrc}`);
    return { success: true, path: target };
  } catch (error) {
    logger.error('Failed to install OpenClaw CLI:', error);
    return { success: false, error: String(error) };
  }
}

// ── Auto-install on first launch ─────────────────────────────────────────────

function isCliInstalled(): boolean {
  const platform = process.platform;

  if (platform === 'win32') return true; // handled by NSIS installer

  const target = getCliTargetPath();
  if (!existsSync(target)) return false;

  // Also check /usr/local/bin/openclaw for deb installs
  if (platform === 'linux' && existsSync('/usr/local/bin/openclaw')) return true;

  return true;
}

function ensureWindowsCliOnPath(): Promise<'updated' | 'already-present'> {
  return new Promise((resolve, reject) => {
    const cliWrapper = getPackagedCliWrapperPath();
    if (!cliWrapper) {
      reject(new Error('CLI wrapper not found in app resources.'));
      return;
    }

    const cliDir = dirname(cliWrapper);
    const helperPath = join(cliDir, 'update-user-path.ps1');
    if (!existsSync(helperPath)) {
      reject(new Error(`PATH helper not found at ${helperPath}`));
      return;
    }

    const child = spawn(
      getWindowsPowerShellPath(),
      [
        '-NoProfile',
        '-NonInteractive',
        '-ExecutionPolicy',
        'Bypass',
        '-File',
        helperPath,
        '-Action',
        'add',
        '-CliDir',
        cliDir,
      ],
      {
        env: process.env,
        stdio: ['ignore', 'pipe', 'pipe'],
        windowsHide: true,
      },
    );

    let stdout = '';
    let stderr = '';

    child.stdout.on('data', (chunk) => {
      stdout += chunk.toString();
    });

    child.stderr.on('data', (chunk) => {
      stderr += chunk.toString();
    });

    child.on('error', reject);
    child.on('close', (code) => {
      if (code !== 0) {
        reject(new Error(stderr.trim() || `PowerShell exited with code ${code}`));
        return;
      }

      const status = stdout.trim();
      if (status === 'updated' || status === 'already-present') {
        resolve(status);
        return;
      }

      reject(new Error(`Unexpected PowerShell output: ${status || '(empty)'}`));
    });
  });
}

function ensureLocalBinInPath(): void {
  if (process.platform === 'win32') return;

  const localBin = join(homedir(), '.local', 'bin');
  const pathEnv = process.env.PATH || '';
  if (pathEnv.split(':').includes(localBin)) return;

  const shell = process.env.SHELL || '/bin/zsh';
  const profileFile = shell.includes('zsh')
    ? join(homedir(), '.zshrc')
    : shell.includes('fish')
      ? join(homedir(), '.config', 'fish', 'config.fish')
      : join(homedir(), '.bashrc');

  try {
    const marker = '.local/bin';
    let content = '';
    try {
      content = readFileSync(profileFile, 'utf-8');
    } catch {
      // file doesn't exist yet
    }

    if (content.includes(marker)) return;

    const line = shell.includes('fish')
      ? '\n# Added by ClawX\nfish_add_path "$HOME/.local/bin"\n'
      : '\n# Added by ClawX\nexport PATH="$HOME/.local/bin:$PATH"\n';

    appendFileSync(profileFile, line);
    logger.info(`Added ~/.local/bin to PATH in ${profileFile}`);
  } catch (error) {
    logger.warn('Failed to add ~/.local/bin to PATH:', error);
  }
}

export async function autoInstallCliIfNeeded(
  notify?: (path: string) => void,
): Promise<void> {
  if (!app.isPackaged) return;
  if (process.platform === 'win32') {
    try {
      const result = await ensureWindowsCliOnPath();
      if (result === 'updated') {
        logger.info('Added Windows CLI directory to user PATH.');
      }
    } catch (error) {
      logger.warn('Failed to ensure Windows CLI is on PATH:', error);
    }
    return;
  }

  const target = getCliTargetPath();
  const wrapperSrc = getPackagedCliWrapperPath();

  if (isCliInstalled()) {
    if (target && wrapperSrc && existsSync(target)) {
      try {
        unlinkSync(target);
        symlinkSync(wrapperSrc, target);
        logger.debug(`Refreshed CLI symlink: ${target} -> ${wrapperSrc}`);
      } catch {
        // non-critical
      }
    }
    return;
  }

  logger.info('Auto-installing openclaw CLI...');
  const result = await installOpenClawCli();
  if (result.success) {
    logger.info(`CLI auto-installed at ${result.path}`);
    ensureLocalBinInPath();
    if (result.path) notify?.(result.path);
  } else {
    logger.warn(`CLI auto-install failed: ${result.error}`);
  }
}

// ── Completion helpers ───────────────────────────────────────────────────────

function getNodeExecForCli(): string {
  if (process.platform === 'darwin' && app.isPackaged) {
    const appName = app.getName();
    const helperName = `${appName} Helper`;
    const helperPath = join(
      dirname(process.execPath),
      '../Frameworks',
      `${helperName}.app`,
      'Contents/MacOS',
      helperName,
    );
    if (existsSync(helperPath)) return helperPath;
  }
  return process.execPath;
}

export function generateCompletionCache(): void {
  if (!app.isPackaged) return;

  const entryPath = getOpenClawEntryPath();
  if (!existsSync(entryPath)) return;

  const execPath = getNodeExecForCli();

  const child = spawn(execPath, [entryPath, 'completion', '--write-state'], {
    env: {
      ...process.env,
      ELECTRON_RUN_AS_NODE: '1',
      OPENCLAW_NO_RESPAWN: '1',
      OPENCLAW_EMBEDDED_IN: 'ClawX',
    },
    stdio: 'ignore',
    detached: false,
    windowsHide: true,
  });

  child.on('close', (code) => {
    if (code === 0) {
      logger.info('OpenClaw completion cache generated');
    } else {
      logger.warn(`OpenClaw completion cache generation exited with code ${code}`);
    }
  });

  child.on('error', (err) => {
    logger.warn('Failed to generate completion cache:', err);
  });
}

export function installCompletionToProfile(): void {
  if (!app.isPackaged) return;
  if (process.platform === 'win32') return;

  const entryPath = getOpenClawEntryPath();
  if (!existsSync(entryPath)) return;

  const execPath = getNodeExecForCli();

  const child = spawn(
    execPath,
    [entryPath, 'completion', '--install', '-y'],
    {
      env: {
        ...process.env,
        ELECTRON_RUN_AS_NODE: '1',
        OPENCLAW_NO_RESPAWN: '1',
        OPENCLAW_EMBEDDED_IN: 'ClawX',
      },
      stdio: 'ignore',
      detached: false,
      windowsHide: true,
    }
  );

  child.on('close', (code) => {
    if (code === 0) {
      logger.info('OpenClaw completion installed to shell profile');
    } else {
      logger.warn(`OpenClaw completion install exited with code ${code}`);
    }
  });

  child.on('error', (err) => {
    logger.warn('Failed to install completion to shell profile:', err);
  });
}
