/**
 * System Paths Configuration
 *
 * Centralized configuration for ALL system paths that automaker needs to access
 * outside of the ALLOWED_ROOT_DIRECTORY. These are well-known system paths for
 * tools like GitHub CLI, Claude CLI, Node.js version managers, etc.
 *
 * ALL file system access must go through this module or secureFs.
 * Direct fs imports are NOT allowed anywhere else in the codebase.
 *
 * Categories of system paths:
 * 1. CLI Tools: GitHub CLI, Claude CLI
 * 2. Version Managers: NVM, fnm, Volta
 * 3. Shells: /bin/zsh, /bin/bash, PowerShell
 * 4. Electron userData: API keys, window bounds, app settings
 * 5. Script directories: node_modules, logs (relative to script)
 */

import os from 'os';
import path from 'path';
import fsSync from 'fs';
import fs from 'fs/promises';

// =============================================================================
// System Tool Path Definitions
// =============================================================================

/**
 * Get common paths where GitHub CLI might be installed
 */
export function getGitHubCliPaths(): string[] {
  const isWindows = process.platform === 'win32';

  if (isWindows) {
    return [
      path.join(process.env.LOCALAPPDATA || '', 'Programs', 'gh', 'bin', 'gh.exe'),
      path.join(process.env.ProgramFiles || '', 'GitHub CLI', 'gh.exe'),
    ].filter(Boolean);
  }

  return [
    '/opt/homebrew/bin/gh',
    '/usr/local/bin/gh',
    path.join(os.homedir(), '.local', 'bin', 'gh'),
    '/home/linuxbrew/.linuxbrew/bin/gh',
  ];
}

/**
 * Get common paths where Claude CLI might be installed
 */
export function getClaudeCliPaths(): string[] {
  const isWindows = process.platform === 'win32';

  if (isWindows) {
    const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
    return [
      path.join(os.homedir(), '.local', 'bin', 'claude.exe'),
      path.join(appData, 'npm', 'claude.cmd'),
      path.join(appData, 'npm', 'claude'),
      path.join(appData, '.npm-global', 'bin', 'claude.cmd'),
      path.join(appData, '.npm-global', 'bin', 'claude'),
    ];
  }

  return [
    path.join(os.homedir(), '.local', 'bin', 'claude'),
    path.join(os.homedir(), '.claude', 'local', 'claude'),
    '/usr/local/bin/claude',
    path.join(os.homedir(), '.npm-global', 'bin', 'claude'),
  ];
}

/**
 * Get NVM-installed Node.js bin paths for CLI tools
 */
function getNvmBinPaths(): string[] {
  const nvmDir = process.env.NVM_DIR || path.join(os.homedir(), '.nvm');
  const versionsDir = path.join(nvmDir, 'versions', 'node');

  try {
    if (!fsSync.existsSync(versionsDir)) {
      return [];
    }
    const versions = fsSync.readdirSync(versionsDir);
    return versions.map((version) => path.join(versionsDir, version, 'bin'));
  } catch {
    return [];
  }
}

/**
 * Get fnm (Fast Node Manager) installed Node.js bin paths
 */
function getFnmBinPaths(): string[] {
  const homeDir = os.homedir();
  const possibleFnmDirs = [
    path.join(homeDir, '.local', 'share', 'fnm', 'node-versions'),
    path.join(homeDir, '.fnm', 'node-versions'),
    // macOS
    path.join(homeDir, 'Library', 'Application Support', 'fnm', 'node-versions'),
  ];

  const binPaths: string[] = [];

  for (const fnmDir of possibleFnmDirs) {
    try {
      if (!fsSync.existsSync(fnmDir)) {
        continue;
      }
      const versions = fsSync.readdirSync(fnmDir);
      for (const version of versions) {
        binPaths.push(path.join(fnmDir, version, 'installation', 'bin'));
      }
    } catch {
      // Ignore errors for this directory
    }
  }

  return binPaths;
}

/**
 * Get common paths where Codex CLI might be installed
 */
export function getCodexCliPaths(): string[] {
  const isWindows = process.platform === 'win32';
  const homeDir = os.homedir();

  if (isWindows) {
    const appData = process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming');
    const localAppData = process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local');
    return [
      path.join(homeDir, '.local', 'bin', 'codex.exe'),
      path.join(appData, 'npm', 'codex.cmd'),
      path.join(appData, 'npm', 'codex'),
      path.join(appData, '.npm-global', 'bin', 'codex.cmd'),
      path.join(appData, '.npm-global', 'bin', 'codex'),
      // Volta on Windows
      path.join(homeDir, '.volta', 'bin', 'codex.exe'),
      // pnpm on Windows
      path.join(localAppData, 'pnpm', 'codex.cmd'),
      path.join(localAppData, 'pnpm', 'codex'),
    ];
  }

  // Include NVM bin paths for codex installed via npm global under NVM
  const nvmBinPaths = getNvmBinPaths().map((binPath) => path.join(binPath, 'codex'));

  // Include fnm bin paths
  const fnmBinPaths = getFnmBinPaths().map((binPath) => path.join(binPath, 'codex'));

  // pnpm global bin path
  const pnpmHome = process.env.PNPM_HOME || path.join(homeDir, '.local', 'share', 'pnpm');

  return [
    // Standard locations
    path.join(homeDir, '.local', 'bin', 'codex'),
    '/opt/homebrew/bin/codex',
    '/usr/local/bin/codex',
    '/usr/bin/codex',
    path.join(homeDir, '.npm-global', 'bin', 'codex'),
    // Linuxbrew
    '/home/linuxbrew/.linuxbrew/bin/codex',
    // Volta
    path.join(homeDir, '.volta', 'bin', 'codex'),
    // pnpm global
    path.join(pnpmHome, 'codex'),
    // Yarn global
    path.join(homeDir, '.yarn', 'bin', 'codex'),
    path.join(homeDir, '.config', 'yarn', 'global', 'node_modules', '.bin', 'codex'),
    // Snap packages
    '/snap/bin/codex',
    // NVM paths
    ...nvmBinPaths,
    // fnm paths
    ...fnmBinPaths,
  ];
}

const CODEX_CONFIG_DIR_NAME = '.codex';
const CODEX_AUTH_FILENAME = 'auth.json';
const CODEX_TOKENS_KEY = 'tokens';

/**
 * Get the Codex configuration directory path
 */
export function getCodexConfigDir(): string {
  return path.join(os.homedir(), CODEX_CONFIG_DIR_NAME);
}

/**
 * Get path to Codex auth file
 */
export function getCodexAuthPath(): string {
  return path.join(getCodexConfigDir(), CODEX_AUTH_FILENAME);
}

/**
 * Get the Claude configuration directory path
 */
export function getClaudeConfigDir(): string {
  return path.join(os.homedir(), '.claude');
}

/**
 * Get paths to Claude credential files
 */
export function getClaudeCredentialPaths(): string[] {
  const claudeDir = getClaudeConfigDir();
  return [path.join(claudeDir, '.credentials.json'), path.join(claudeDir, 'credentials.json')];
}

/**
 * Get path to Claude settings file
 */
export function getClaudeSettingsPath(): string {
  return path.join(getClaudeConfigDir(), 'settings.json');
}

/**
 * Get path to Claude stats cache file
 */
export function getClaudeStatsCachePath(): string {
  return path.join(getClaudeConfigDir(), 'stats-cache.json');
}

/**
 * Get path to Claude projects/sessions directory
 */
export function getClaudeProjectsDir(): string {
  return path.join(getClaudeConfigDir(), 'projects');
}

/**
 * Enumerate directories matching a prefix pattern and return full paths
 * Used to resolve dynamic directory names like version numbers
 */
function enumerateMatchingPaths(
  parentDir: string,
  prefix: string,
  ...subPathParts: string[]
): string[] {
  try {
    if (!fsSync.existsSync(parentDir)) {
      return [];
    }
    const entries = fsSync.readdirSync(parentDir);
    const matching = entries.filter((entry) => entry.startsWith(prefix));
    return matching.map((entry) => path.join(parentDir, entry, ...subPathParts));
  } catch {
    return [];
  }
}

/**
 * Get common Git Bash installation paths on Windows
 * Git Bash is needed for running shell scripts cross-platform
 */
export function getGitBashPaths(): string[] {
  if (process.platform !== 'win32') {
    return [];
  }

  const homeDir = os.homedir();
  const localAppData = process.env.LOCALAPPDATA || '';

  // Dynamic paths that require directory enumeration
  // winget installs to: LocalAppData\Microsoft\WinGet\Packages\Git.Git_<hash>\bin\bash.exe
  const wingetGitPaths = localAppData
    ? enumerateMatchingPaths(
        path.join(localAppData, 'Microsoft', 'WinGet', 'Packages'),
        'Git.Git_',
        'bin',
        'bash.exe'
      )
    : [];

  // GitHub Desktop bundles Git at: LocalAppData\GitHubDesktop\app-<version>\resources\app\git\cmd\bash.exe
  const githubDesktopPaths = localAppData
    ? enumerateMatchingPaths(
        path.join(localAppData, 'GitHubDesktop'),
        'app-',
        'resources',
        'app',
        'git',
        'cmd',
        'bash.exe'
      )
    : [];

  return [
    // Standard Git for Windows installations
    'C:\\Program Files\\Git\\bin\\bash.exe',
    'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
    // User-local installations
    path.join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe'),
    // Scoop package manager
    path.join(homeDir, 'scoop', 'apps', 'git', 'current', 'bin', 'bash.exe'),
    // Chocolatey
    path.join(
      process.env.ChocolateyInstall || 'C:\\ProgramData\\chocolatey',
      'lib',
      'git',
      'tools',
      'bin',
      'bash.exe'
    ),
    // winget installations (dynamically resolved)
    ...wingetGitPaths,
    // GitHub Desktop bundled Git (dynamically resolved)
    ...githubDesktopPaths,
  ].filter(Boolean);
}

/**
 * Get common shell paths for shell detection
 * Includes both full paths and short names to match $SHELL or PATH entries
 */
export function getShellPaths(): string[] {
  if (process.platform === 'win32') {
    return [
      // Full paths (most specific first)
      'C:\\Program Files\\PowerShell\\7\\pwsh.exe',
      'C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe',
      'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
      // COMSPEC environment variable (typically cmd.exe)
      process.env.COMSPEC || 'C:\\Windows\\System32\\cmd.exe',
      // Short names (for PATH resolution)
      'pwsh.exe',
      'pwsh',
      'powershell.exe',
      'powershell',
      'cmd.exe',
      'cmd',
    ];
  }

  // POSIX (macOS, Linux)
  return [
    // Full paths
    '/bin/zsh',
    '/bin/bash',
    '/bin/sh',
    '/usr/bin/zsh',
    '/usr/bin/bash',
    '/usr/bin/sh',
    '/usr/local/bin/zsh',
    '/usr/local/bin/bash',
    '/opt/homebrew/bin/zsh',
    '/opt/homebrew/bin/bash',
    // Short names (for PATH resolution or $SHELL matching)
    'zsh',
    'bash',
    'sh',
  ];
}

// =============================================================================
// Node.js Version Manager Paths
// =============================================================================

/**
 * Get NVM installation paths
 */
export function getNvmPaths(): string[] {
  const homeDir = os.homedir();

  if (process.platform === 'win32') {
    const appData = process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming');
    return [path.join(appData, 'nvm')];
  }

  return [path.join(homeDir, '.nvm', 'versions', 'node')];
}

/**
 * Get fnm installation paths
 */
export function getFnmPaths(): string[] {
  const homeDir = os.homedir();

  if (process.platform === 'win32') {
    const localAppData = process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local');
    return [
      path.join(homeDir, '.fnm', 'node-versions'),
      path.join(localAppData, 'fnm', 'node-versions'),
    ];
  }

  if (process.platform === 'darwin') {
    return [
      path.join(homeDir, '.local', 'share', 'fnm', 'node-versions'),
      path.join(homeDir, 'Library', 'Application Support', 'fnm', 'node-versions'),
    ];
  }

  return [
    path.join(homeDir, '.local', 'share', 'fnm', 'node-versions'),
    path.join(homeDir, '.fnm', 'node-versions'),
  ];
}

/**
 * Get common Node.js installation paths (not version managers)
 */
export function getNodeSystemPaths(): string[] {
  if (process.platform === 'win32') {
    return [
      path.join(process.env.PROGRAMFILES || 'C:\\Program Files', 'nodejs', 'node.exe'),
      path.join(
        process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)',
        'nodejs',
        'node.exe'
      ),
    ];
  }

  if (process.platform === 'darwin') {
    return ['/opt/homebrew/bin/node', '/usr/local/bin/node', '/usr/bin/node'];
  }

  // Linux
  return ['/usr/bin/node', '/usr/local/bin/node', '/snap/bin/node'];
}

/**
 * Get Scoop installation path for Node.js (Windows)
 */
export function getScoopNodePath(): string {
  return path.join(os.homedir(), 'scoop', 'apps', 'nodejs', 'current', 'node.exe');
}

/**
 * Get Chocolatey installation path for Node.js (Windows)
 */
export function getChocolateyNodePath(): string {
  return path.join(
    process.env.ChocolateyInstall || 'C:\\ProgramData\\chocolatey',
    'bin',
    'node.exe'
  );
}

/**
 * Get WSL detection path
 */
export function getWslVersionPath(): string {
  return '/proc/version';
}

/**
 * Extended PATH environment for finding system tools
 */
export function getExtendedPath(): string {
  const paths = [
    process.env.PATH,
    '/opt/homebrew/bin',
    '/usr/local/bin',
    '/home/linuxbrew/.linuxbrew/bin',
    `${process.env.HOME}/.local/bin`,
  ];

  return paths.filter(Boolean).join(process.platform === 'win32' ? ';' : ':');
}

// =============================================================================
// System Path Access Methods (Unconstrained - only for system tool detection)
// =============================================================================

/**
 * Check if a file exists at a system path (synchronous)
 * IMPORTANT: This bypasses ALLOWED_ROOT_DIRECTORY restrictions.
 * Only use for checking system tool installation paths.
 */
export function systemPathExists(filePath: string): boolean {
  if (!isAllowedSystemPath(filePath)) {
    throw new Error(`[SystemPaths] Access denied: ${filePath} is not an allowed system path`);
  }
  return fsSync.existsSync(filePath);
}

/**
 * Check if a file is accessible at a system path (async)
 * IMPORTANT: This bypasses ALLOWED_ROOT_DIRECTORY restrictions.
 * Only use for checking system tool installation paths.
 */
export async function systemPathAccess(filePath: string): Promise<boolean> {
  if (!isAllowedSystemPath(filePath)) {
    throw new Error(`[SystemPaths] Access denied: ${filePath} is not an allowed system path`);
  }
  try {
    await fs.access(filePath);
    return true;
  } catch {
    return false;
  }
}

/**
 * Check if a file has execute permission (synchronous)
 * On Windows, only checks existence (X_OK is not meaningful)
 */
export function systemPathIsExecutable(filePath: string): boolean {
  if (!isAllowedSystemPath(filePath)) {
    throw new Error(`[SystemPaths] Access denied: ${filePath} is not an allowed system path`);
  }
  try {
    if (process.platform === 'win32') {
      fsSync.accessSync(filePath, fsSync.constants.F_OK);
    } else {
      fsSync.accessSync(filePath, fsSync.constants.X_OK);
    }
    return true;
  } catch {
    return false;
  }
}

/**
 * Read a file from an allowed system path (async)
 * IMPORTANT: This bypasses ALLOWED_ROOT_DIRECTORY restrictions.
 * Only use for reading Claude config files and similar system configs.
 */
export async function systemPathReadFile(
  filePath: string,
  encoding: BufferEncoding = 'utf-8'
): Promise<string> {
  if (!isAllowedSystemPath(filePath)) {
    throw new Error(`[SystemPaths] Access denied: ${filePath} is not an allowed system path`);
  }
  return fs.readFile(filePath, encoding);
}

/**
 * Read a file from an allowed system path (synchronous)
 */
export function systemPathReadFileSync(
  filePath: string,
  encoding: BufferEncoding = 'utf-8'
): string {
  if (!isAllowedSystemPath(filePath)) {
    throw new Error(`[SystemPaths] Access denied: ${filePath} is not an allowed system path`);
  }
  return fsSync.readFileSync(filePath, encoding);
}

/**
 * Write a file to an allowed system path (synchronous)
 */
export function systemPathWriteFileSync(
  filePath: string,
  data: string,
  options?: { encoding?: BufferEncoding; mode?: number }
): void {
  if (!isAllowedSystemPath(filePath)) {
    throw new Error(`[SystemPaths] Access denied: ${filePath} is not an allowed system path`);
  }
  fsSync.writeFileSync(filePath, data, options);
}

/**
 * Read directory contents from an allowed system path (async)
 * IMPORTANT: This bypasses ALLOWED_ROOT_DIRECTORY restrictions.
 */
export async function systemPathReaddir(dirPath: string): Promise<string[]> {
  if (!isAllowedSystemPath(dirPath)) {
    throw new Error(`[SystemPaths] Access denied: ${dirPath} is not an allowed system path`);
  }
  return fs.readdir(dirPath);
}

/**
 * Read directory contents from an allowed system path (synchronous)
 */
export function systemPathReaddirSync(dirPath: string): string[] {
  if (!isAllowedSystemPath(dirPath)) {
    throw new Error(`[SystemPaths] Access denied: ${dirPath} is not an allowed system path`);
  }
  return fsSync.readdirSync(dirPath);
}

/**
 * Get file stats from a system path (synchronous)
 */
export function systemPathStatSync(filePath: string): fsSync.Stats {
  if (!isAllowedSystemPath(filePath)) {
    throw new Error(`[SystemPaths] Access denied: ${filePath} is not an allowed system path`);
  }
  return fsSync.statSync(filePath);
}

/**
 * Get file stats from a system path (async)
 */
export async function systemPathStat(filePath: string): Promise<fsSync.Stats> {
  if (!isAllowedSystemPath(filePath)) {
    throw new Error(`[SystemPaths] Access denied: ${filePath} is not an allowed system path`);
  }
  return fs.stat(filePath);
}

// =============================================================================
// Path Validation
// =============================================================================

/**
 * All paths that are allowed for system tool detection
 */
function getAllAllowedSystemPaths(): string[] {
  return [
    // GitHub CLI paths
    ...getGitHubCliPaths(),
    // Claude CLI paths
    ...getClaudeCliPaths(),
    // Claude config directory and files
    getClaudeConfigDir(),
    ...getClaudeCredentialPaths(),
    getClaudeSettingsPath(),
    getClaudeStatsCachePath(),
    getClaudeProjectsDir(),
    // Codex CLI paths
    ...getCodexCliPaths(),
    // Codex config directory and files
    getCodexConfigDir(),
    getCodexAuthPath(),
    // OpenCode CLI paths
    ...getOpenCodeCliPaths(),
    // OpenCode config directory and files
    getOpenCodeConfigDir(),
    getOpenCodeAuthPath(),
    // Shell paths
    ...getShellPaths(),
    // Git Bash paths (for Windows cross-platform shell script execution)
    ...getGitBashPaths(),
    // Node.js system paths
    ...getNodeSystemPaths(),
    getScoopNodePath(),
    getChocolateyNodePath(),
    // WSL detection
    getWslVersionPath(),
  ];
}

/**
 * Get all allowed directories (for recursive access)
 */
function getAllAllowedSystemDirs(): string[] {
  return [
    // Claude config
    getClaudeConfigDir(),
    getClaudeProjectsDir(),
    // Codex config
    getCodexConfigDir(),
    // OpenCode config
    getOpenCodeConfigDir(),
    // Version managers (need recursive access for version directories)
    ...getNvmPaths(),
    ...getFnmPaths(),
  ];
}

/**
 * Check if a path is an allowed system path
 * Paths must either be exactly in the allowed list, or be inside an allowed directory
 */
export function isAllowedSystemPath(filePath: string): boolean {
  const normalizedPath = path.resolve(filePath);
  const allowedPaths = getAllAllowedSystemPaths();

  // Check for exact match
  if (allowedPaths.includes(normalizedPath)) {
    return true;
  }

  // Check if the path is inside an allowed directory
  const allowedDirs = getAllAllowedSystemDirs();

  for (const allowedDir of allowedDirs) {
    const normalizedAllowedDir = path.resolve(allowedDir);
    // Check if path is exactly the allowed dir or inside it
    if (
      normalizedPath === normalizedAllowedDir ||
      normalizedPath.startsWith(normalizedAllowedDir + path.sep)
    ) {
      return true;
    }
  }

  return false;
}

// =============================================================================
// Electron userData Operations
// =============================================================================

// Store the Electron userData path (set by Electron main process)
let electronUserDataPath: string | null = null;

/**
 * Set the Electron userData path (called from Electron main process)
 */
export function setElectronUserDataPath(userDataPath: string): void {
  electronUserDataPath = userDataPath;
}

/**
 * Get the Electron userData path
 */
export function getElectronUserDataPath(): string | null {
  return electronUserDataPath;
}

/**
 * Check if a path is within the Electron userData directory
 */
export function isElectronUserDataPath(filePath: string): boolean {
  if (!electronUserDataPath) return false;
  const normalizedPath = path.resolve(filePath);
  const normalizedUserData = path.resolve(electronUserDataPath);
  return (
    normalizedPath === normalizedUserData ||
    normalizedPath.startsWith(normalizedUserData + path.sep)
  );
}

/**
 * Read a file from Electron userData directory
 */
export function electronUserDataReadFileSync(
  relativePath: string,
  encoding: BufferEncoding = 'utf-8'
): string {
  if (!electronUserDataPath) {
    throw new Error('[SystemPaths] Electron userData path not initialized');
  }
  const fullPath = path.join(electronUserDataPath, relativePath);
  return fsSync.readFileSync(fullPath, encoding);
}

/**
 * Write a file to Electron userData directory
 */
export function electronUserDataWriteFileSync(
  relativePath: string,
  data: string,
  options?: { encoding?: BufferEncoding; mode?: number }
): void {
  if (!electronUserDataPath) {
    throw new Error('[SystemPaths] Electron userData path not initialized');
  }
  const fullPath = path.join(electronUserDataPath, relativePath);
  fsSync.writeFileSync(fullPath, data, options);
}

/**
 * Check if a file exists in Electron userData directory
 */
export function electronUserDataExists(relativePath: string): boolean {
  if (!electronUserDataPath) return false;
  const fullPath = path.join(electronUserDataPath, relativePath);
  return fsSync.existsSync(fullPath);
}

// =============================================================================
// Script Directory Operations (for init.mjs and similar)
// =============================================================================

// Store the script's base directory
let scriptBaseDir: string | null = null;

/**
 * Set the script base directory
 */
export function setScriptBaseDir(baseDir: string): void {
  scriptBaseDir = baseDir;
}

/**
 * Get the script base directory
 */
export function getScriptBaseDir(): string | null {
  return scriptBaseDir;
}

/**
 * Check if a file exists relative to script base directory
 */
export function scriptDirExists(relativePath: string): boolean {
  if (!scriptBaseDir) {
    throw new Error('[SystemPaths] Script base directory not initialized');
  }
  const fullPath = path.join(scriptBaseDir, relativePath);
  return fsSync.existsSync(fullPath);
}

/**
 * Create a directory relative to script base directory
 */
export function scriptDirMkdirSync(relativePath: string, options?: { recursive?: boolean }): void {
  if (!scriptBaseDir) {
    throw new Error('[SystemPaths] Script base directory not initialized');
  }
  const fullPath = path.join(scriptBaseDir, relativePath);
  fsSync.mkdirSync(fullPath, options);
}

/**
 * Create a write stream for a file relative to script base directory
 */
export function scriptDirCreateWriteStream(relativePath: string): fsSync.WriteStream {
  if (!scriptBaseDir) {
    throw new Error('[SystemPaths] Script base directory not initialized');
  }
  const fullPath = path.join(scriptBaseDir, relativePath);
  return fsSync.createWriteStream(fullPath);
}

// =============================================================================
// Electron App Bundle Operations (for accessing app's own files)
// =============================================================================

// Store the Electron app bundle paths (can have multiple allowed directories)
let electronAppDirs: string[] = [];
let electronResourcesPath: string | null = null;

/**
 * Set the Electron app directories (called from Electron main process)
 * In development mode, pass the project root to allow access to source files.
 * In production mode, pass __dirname and process.resourcesPath.
 *
 * @param appDirOrDirs - Single directory or array of directories to allow
 * @param resourcesPath - Optional resources path (for packaged apps)
 */
export function setElectronAppPaths(appDirOrDirs: string | string[], resourcesPath?: string): void {
  electronAppDirs = Array.isArray(appDirOrDirs) ? appDirOrDirs : [appDirOrDirs];
  electronResourcesPath = resourcesPath || null;
}

/**
 * Check if a path is within the Electron app bundle (any of the allowed directories)
 */
function isElectronAppPath(filePath: string): boolean {
  const normalizedPath = path.resolve(filePath);

  // Check against all allowed app directories
  for (const appDir of electronAppDirs) {
    const normalizedAppDir = path.resolve(appDir);
    if (
      normalizedPath === normalizedAppDir ||
      normalizedPath.startsWith(normalizedAppDir + path.sep)
    ) {
      return true;
    }
  }

  // Check against resources path (for packaged apps)
  if (electronResourcesPath) {
    const normalizedResources = path.resolve(electronResourcesPath);
    if (
      normalizedPath === normalizedResources ||
      normalizedPath.startsWith(normalizedResources + path.sep)
    ) {
      return true;
    }
  }

  return false;
}

/**
 * Check if a file exists within the Electron app bundle
 */
export function electronAppExists(filePath: string): boolean {
  if (!isElectronAppPath(filePath)) {
    throw new Error(`[SystemPaths] Access denied: ${filePath} is not within Electron app bundle`);
  }
  return fsSync.existsSync(filePath);
}

/**
 * Read a file from the Electron app bundle
 */
export function electronAppReadFileSync(filePath: string): Buffer {
  if (!isElectronAppPath(filePath)) {
    throw new Error(`[SystemPaths] Access denied: ${filePath} is not within Electron app bundle`);
  }
  return fsSync.readFileSync(filePath);
}

/**
 * Get file stats from the Electron app bundle
 */
export function electronAppStatSync(filePath: string): fsSync.Stats {
  if (!isElectronAppPath(filePath)) {
    throw new Error(`[SystemPaths] Access denied: ${filePath} is not within Electron app bundle`);
  }
  return fsSync.statSync(filePath);
}

/**
 * Get file stats from the Electron app bundle (async with callback for compatibility)
 */
export function electronAppStat(
  filePath: string,
  callback: (err: NodeJS.ErrnoException | null, stats: fsSync.Stats | undefined) => void
): void {
  if (!isElectronAppPath(filePath)) {
    callback(
      new Error(`[SystemPaths] Access denied: ${filePath} is not within Electron app bundle`),
      undefined
    );
    return;
  }
  fsSync.stat(filePath, callback);
}

/**
 * Read a file from the Electron app bundle (async with callback for compatibility)
 */
export function electronAppReadFile(
  filePath: string,
  callback: (err: NodeJS.ErrnoException | null, data: Buffer | undefined) => void
): void {
  if (!isElectronAppPath(filePath)) {
    callback(
      new Error(`[SystemPaths] Access denied: ${filePath} is not within Electron app bundle`),
      undefined
    );
    return;
  }
  fsSync.readFile(filePath, callback);
}

// =============================================================================
// High-level Tool Detection Methods
// =============================================================================

/**
 * Find the first existing path from a list of system paths
 */
export async function findFirstExistingPath(paths: string[]): Promise<string | null> {
  for (const p of paths) {
    if (await systemPathAccess(p)) {
      return p;
    }
  }
  return null;
}

/**
 * Check if GitHub CLI is installed and return its path
 */
export async function findGitHubCliPath(): Promise<string | null> {
  return findFirstExistingPath(getGitHubCliPaths());
}

/**
 * Check if Claude CLI is installed and return its path
 */
export async function findClaudeCliPath(): Promise<string | null> {
  return findFirstExistingPath(getClaudeCliPaths());
}

export async function findCodexCliPath(): Promise<string | null> {
  return findFirstExistingPath(getCodexCliPaths());
}

/**
 * Find Git Bash on Windows and return its path
 */
export async function findGitBashPath(): Promise<string | null> {
  return findFirstExistingPath(getGitBashPaths());
}

/**
 * Get Claude authentication status by checking various indicators
 */
export interface ClaudeAuthIndicators {
  hasCredentialsFile: boolean;
  hasSettingsFile: boolean;
  hasStatsCacheWithActivity: boolean;
  hasProjectsSessions: boolean;
  credentials: {
    hasOAuthToken: boolean;
    hasApiKey: boolean;
  } | null;
}

export async function getClaudeAuthIndicators(): Promise<ClaudeAuthIndicators> {
  const result: ClaudeAuthIndicators = {
    hasCredentialsFile: false,
    hasSettingsFile: false,
    hasStatsCacheWithActivity: false,
    hasProjectsSessions: false,
    credentials: null,
  };

  // Check settings file
  try {
    if (await systemPathAccess(getClaudeSettingsPath())) {
      result.hasSettingsFile = true;
    }
  } catch {
    // Ignore errors
  }

  // Check stats cache for recent activity
  try {
    const statsContent = await systemPathReadFile(getClaudeStatsCachePath());
    const stats = JSON.parse(statsContent);
    if (stats.dailyActivity && stats.dailyActivity.length > 0) {
      result.hasStatsCacheWithActivity = true;
    }
  } catch {
    // Ignore errors
  }

  // Check for sessions in projects directory
  try {
    const sessions = await systemPathReaddir(getClaudeProjectsDir());
    if (sessions.length > 0) {
      result.hasProjectsSessions = true;
    }
  } catch {
    // Ignore errors
  }

  // Check credentials files
  const credentialPaths = getClaudeCredentialPaths();
  for (const credPath of credentialPaths) {
    try {
      const content = await systemPathReadFile(credPath);
      const credentials = JSON.parse(content);
      result.hasCredentialsFile = true;
      // Support multiple credential formats:
      // 1. Claude Code CLI format: { claudeAiOauth: { accessToken, refreshToken } }
      // 2. Legacy format: { oauth_token } or { access_token }
      // 3. API key format: { api_key }
      const hasClaudeOauth = !!credentials.claudeAiOauth?.accessToken;
      const hasLegacyOauth = !!(credentials.oauth_token || credentials.access_token);
      result.credentials = {
        hasOAuthToken: hasClaudeOauth || hasLegacyOauth,
        hasApiKey: !!credentials.api_key,
      };
      break;
    } catch {
      // Continue to next path
    }
  }

  return result;
}

export interface CodexAuthIndicators {
  hasAuthFile: boolean;
  hasOAuthToken: boolean;
  hasApiKey: boolean;
}

const CODEX_OAUTH_KEYS = ['access_token', 'oauth_token'] as const;
const CODEX_API_KEY_KEYS = ['api_key', 'OPENAI_API_KEY'] as const;

function hasNonEmptyStringField(record: Record<string, unknown>, keys: readonly string[]): boolean {
  return keys.some((key) => typeof record[key] === 'string' && record[key]);
}

function getNestedTokens(record: Record<string, unknown>): Record<string, unknown> | null {
  const tokens = record[CODEX_TOKENS_KEY];
  if (tokens && typeof tokens === 'object' && !Array.isArray(tokens)) {
    return tokens as Record<string, unknown>;
  }
  return null;
}

export async function getCodexAuthIndicators(): Promise<CodexAuthIndicators> {
  const result: CodexAuthIndicators = {
    hasAuthFile: false,
    hasOAuthToken: false,
    hasApiKey: false,
  };

  try {
    const authContent = await systemPathReadFile(getCodexAuthPath());
    result.hasAuthFile = true;

    try {
      const authJson = JSON.parse(authContent) as Record<string, unknown>;
      result.hasOAuthToken = hasNonEmptyStringField(authJson, CODEX_OAUTH_KEYS);
      result.hasApiKey = hasNonEmptyStringField(authJson, CODEX_API_KEY_KEYS);
      const nestedTokens = getNestedTokens(authJson);
      if (nestedTokens) {
        result.hasOAuthToken =
          result.hasOAuthToken || hasNonEmptyStringField(nestedTokens, CODEX_OAUTH_KEYS);
        result.hasApiKey =
          result.hasApiKey || hasNonEmptyStringField(nestedTokens, CODEX_API_KEY_KEYS);
      }
    } catch {
      // Ignore parse errors; file exists but contents are unreadable
    }
  } catch {
    // Auth file not found or inaccessible
  }

  return result;
}

// =============================================================================
// OpenCode CLI Detection
// =============================================================================

const OPENCODE_DATA_DIR = '.local/share/opencode';
const OPENCODE_AUTH_FILENAME = 'auth.json';
const OPENCODE_TOKENS_KEY = 'tokens';

/**
 * Get common paths where OpenCode CLI might be installed
 */
export function getOpenCodeCliPaths(): string[] {
  const isWindows = process.platform === 'win32';
  const homeDir = os.homedir();

  if (isWindows) {
    const appData = process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming');
    const localAppData = process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local');
    return [
      // OpenCode's default installation directory
      path.join(homeDir, '.opencode', 'bin', 'opencode.exe'),
      path.join(homeDir, '.local', 'bin', 'opencode.exe'),
      path.join(appData, 'npm', 'opencode.cmd'),
      path.join(appData, 'npm', 'opencode'),
      path.join(appData, '.npm-global', 'bin', 'opencode.cmd'),
      path.join(appData, '.npm-global', 'bin', 'opencode'),
      // Volta on Windows
      path.join(homeDir, '.volta', 'bin', 'opencode.exe'),
      // pnpm on Windows
      path.join(localAppData, 'pnpm', 'opencode.cmd'),
      path.join(localAppData, 'pnpm', 'opencode'),
      // Go installation (if OpenCode is a Go binary)
      path.join(homeDir, 'go', 'bin', 'opencode.exe'),
      path.join(process.env.GOPATH || path.join(homeDir, 'go'), 'bin', 'opencode.exe'),
    ];
  }

  // Include NVM bin paths for opencode installed via npm global under NVM
  const nvmBinPaths = getNvmBinPaths().map((binPath) => path.join(binPath, 'opencode'));

  // Include fnm bin paths
  const fnmBinPaths = getFnmBinPaths().map((binPath) => path.join(binPath, 'opencode'));

  // pnpm global bin path
  const pnpmHome = process.env.PNPM_HOME || path.join(homeDir, '.local', 'share', 'pnpm');

  return [
    // OpenCode's default installation directory
    path.join(homeDir, '.opencode', 'bin', 'opencode'),
    // Standard locations
    path.join(homeDir, '.local', 'bin', 'opencode'),
    '/opt/homebrew/bin/opencode',
    '/usr/local/bin/opencode',
    '/usr/bin/opencode',
    path.join(homeDir, '.npm-global', 'bin', 'opencode'),
    // Linuxbrew
    '/home/linuxbrew/.linuxbrew/bin/opencode',
    // Volta
    path.join(homeDir, '.volta', 'bin', 'opencode'),
    // pnpm global
    path.join(pnpmHome, 'opencode'),
    // Yarn global
    path.join(homeDir, '.yarn', 'bin', 'opencode'),
    path.join(homeDir, '.config', 'yarn', 'global', 'node_modules', '.bin', 'opencode'),
    // Go installation (if OpenCode is a Go binary)
    path.join(homeDir, 'go', 'bin', 'opencode'),
    path.join(process.env.GOPATH || path.join(homeDir, 'go'), 'bin', 'opencode'),
    // Snap packages
    '/snap/bin/opencode',
    // NVM paths
    ...nvmBinPaths,
    // fnm paths
    ...fnmBinPaths,
  ];
}

/**
 * Get the OpenCode data directory path
 * macOS/Linux: ~/.local/share/opencode
 * Windows: %USERPROFILE%\.local\share\opencode
 */
export function getOpenCodeConfigDir(): string {
  return path.join(os.homedir(), OPENCODE_DATA_DIR);
}

/**
 * Get path to OpenCode auth file
 */
export function getOpenCodeAuthPath(): string {
  return path.join(getOpenCodeConfigDir(), OPENCODE_AUTH_FILENAME);
}

/**
 * Check if OpenCode CLI is installed and return its path
 */
export async function findOpenCodeCliPath(): Promise<string | null> {
  return findFirstExistingPath(getOpenCodeCliPaths());
}

export interface OpenCodeAuthIndicators {
  hasAuthFile: boolean;
  hasOAuthToken: boolean;
  hasApiKey: boolean;
}

const OPENCODE_OAUTH_KEYS = ['access_token', 'oauth_token'] as const;
const OPENCODE_API_KEY_KEYS = ['api_key', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY'] as const;

// Provider names that OpenCode uses for provider-specific auth entries
// NOTE: github-copilot uses refresh tokens, so 'access' may be empty but 'refresh' is valid
const OPENCODE_PROVIDERS = [
  'anthropic',
  'openai',
  'google',
  'bedrock',
  'amazon-bedrock',
  'github-copilot',
  'copilot',
] as const;

function getOpenCodeNestedTokens(record: Record<string, unknown>): Record<string, unknown> | null {
  const tokens = record[OPENCODE_TOKENS_KEY];
  if (tokens && typeof tokens === 'object' && !Array.isArray(tokens)) {
    return tokens as Record<string, unknown>;
  }
  return null;
}

/**
 * Check if the auth JSON has provider-specific OAuth credentials
 * OpenCode stores auth in format: { "anthropic": { "type": "oauth", "access": "...", "refresh": "..." } }
 * GitHub Copilot uses refresh tokens, so 'access' may be empty but 'refresh' is valid
 */
function hasProviderOAuth(authJson: Record<string, unknown>): boolean {
  for (const provider of OPENCODE_PROVIDERS) {
    const providerAuth = authJson[provider];
    if (providerAuth && typeof providerAuth === 'object' && !Array.isArray(providerAuth)) {
      const auth = providerAuth as Record<string, unknown>;
      // Check for OAuth type with access token OR refresh token (GitHub Copilot uses refresh tokens)
      if (auth.type === 'oauth') {
        if (
          (typeof auth.access === 'string' && auth.access) ||
          (typeof auth.refresh === 'string' && auth.refresh)
        ) {
          return true;
        }
      }
      // Also check for access_token field directly
      if (typeof auth.access_token === 'string' && auth.access_token) {
        return true;
      }
      // Check for refresh_token field directly
      if (typeof auth.refresh_token === 'string' && auth.refresh_token) {
        return true;
      }
    }
  }
  return false;
}

/**
 * Check if the auth JSON has provider-specific API key credentials
 */
function hasProviderApiKey(authJson: Record<string, unknown>): boolean {
  for (const provider of OPENCODE_PROVIDERS) {
    const providerAuth = authJson[provider];
    if (providerAuth && typeof providerAuth === 'object' && !Array.isArray(providerAuth)) {
      const auth = providerAuth as Record<string, unknown>;
      // Check for API key type
      if (auth.type === 'api_key' && typeof auth.key === 'string' && auth.key) {
        return true;
      }
      // Also check for api_key field directly
      if (typeof auth.api_key === 'string' && auth.api_key) {
        return true;
      }
    }
  }
  return false;
}

/**
 * Get OpenCode authentication status by checking auth file indicators
 */
export async function getOpenCodeAuthIndicators(): Promise<OpenCodeAuthIndicators> {
  const result: OpenCodeAuthIndicators = {
    hasAuthFile: false,
    hasOAuthToken: false,
    hasApiKey: false,
  };

  try {
    const authContent = await systemPathReadFile(getOpenCodeAuthPath());
    result.hasAuthFile = true;

    try {
      const authJson = JSON.parse(authContent) as Record<string, unknown>;

      // Check for legacy top-level keys
      result.hasOAuthToken = hasNonEmptyStringField(authJson, OPENCODE_OAUTH_KEYS);
      result.hasApiKey = hasNonEmptyStringField(authJson, OPENCODE_API_KEY_KEYS);

      // Check for nested tokens object (legacy format)
      const nestedTokens = getOpenCodeNestedTokens(authJson);
      if (nestedTokens) {
        result.hasOAuthToken =
          result.hasOAuthToken || hasNonEmptyStringField(nestedTokens, OPENCODE_OAUTH_KEYS);
        result.hasApiKey =
          result.hasApiKey || hasNonEmptyStringField(nestedTokens, OPENCODE_API_KEY_KEYS);
      }

      // Check for provider-specific auth entries (current OpenCode format)
      // Format: { "anthropic": { "type": "oauth", "access": "...", "refresh": "..." } }
      result.hasOAuthToken = result.hasOAuthToken || hasProviderOAuth(authJson);
      result.hasApiKey = result.hasApiKey || hasProviderApiKey(authJson);
    } catch {
      // Ignore parse errors; file exists but contents are unreadable
    }
  } catch {
    // Auth file not found or inaccessible
  }

  return result;
}
