const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { exec } = require('child_process');
const { promisify } = require('util');
const { spawn } = require('child_process');
const { v4: uuidv4 } = require('uuid');
const { getResolvedAppDir, findClientPath } = require('../core/paths');
const { setupWaylandEnvironment, setupGpuEnvironment } = require('../utils/platformUtils');
const {
  saveInstallPath,
  loadJavaPath,
  getUuidForUser,
  getAuthServerUrl,
  getAuthDomain,
  loadVersionBranch,
  loadVersionClient,
  saveVersionClient,
  loadUsername,
  hasUsername,
  checkLaunchReady,
  loadWrapperConfig,
  generateWrapperScript
} = require('../core/config');
const { resolveJavaPath, getJavaExec, getBundledJavaPath, detectSystemJava, JAVA_EXECUTABLE } = require('./javaManager');
const { getLatestClientVersion } = require('../services/versionManager');
const { FORCE_CLEAN_INSTALL_VERSION, CLEAN_INSTALL_TEST_VERSION } = require('../core/testConfig');
const { ensureGameInstalled } = require('./differentialUpdateManager');
const { syncModsForCurrentProfile } = require('./modManager');
const { getUserDataPath } = require('../utils/userDataMigration');
const { syncServerList } = require('../utils/serverListSync');
const { killGameProcesses } = require('./gameManager');
const { loadAllowMultiInstance } = require('../core/config');

// Client patcher for custom auth server (sanasol.ws)
let clientPatcher = null;
try {
  clientPatcher = require('../utils/clientPatcher');
} catch (err) {
  console.log('[Launcher] Client patcher not available:', err.message);
}

const execAsync = promisify(exec);

// Fetch tokens from the auth server (properly signed with server's Ed25519 key)
async function fetchAuthTokens(uuid, name, password) {
  const authServerUrl = getAuthServerUrl();
  try {
    console.log(`Fetching auth tokens from ${authServerUrl}/game-session/child`);

    const bodyData = {
      uuid: uuid,
      name: name,
      scopes: ['hytale:server', 'hytale:client']
    };
    if (password) bodyData.password = password;

    const response = await fetch(`${authServerUrl}/game-session/child`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(bodyData)
    });

    if (!response.ok) {
      const errBody = await response.json().catch(() => ({}));
      if (response.status === 401 && errBody.password_required) {
        const err = new Error('Password required');
        err.passwordRequired = true;
        err.attemptsRemaining = errBody.attemptsRemaining;
        throw err;
      }
      if (response.status === 429) {
        const err = new Error('Too many failed attempts. Try again later.');
        err.lockedOut = true;
        err.lockoutSeconds = errBody.lockoutSeconds;
        throw err;
      }
      if (response.status === 403 && errBody.username_taken) {
        const err = new Error('This username is reserved by another player who has set a password. Please use a different name.');
        err.usernameTaken = true;
        throw err;
      }
      if (response.status === 403 && errBody.name_locked) {
        const err = new Error(`This UUID is locked to username "${errBody.registeredName}". Change your identity name to match.`);
        err.nameLocked = true;
        err.registeredName = errBody.registeredName;
        throw err;
      }
      throw new Error(`Auth server returned ${response.status}`);
    }

    const data = await response.json();
    const identityToken = data.IdentityToken || data.identityToken;
    const sessionToken = data.SessionToken || data.sessionToken;

    // Verify the identity token has the correct username
    // This catches cases where the auth server defaults to "Player"
    try {
      const parts = identityToken.split('.');
      if (parts.length >= 2) {
        const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
        if (payload.username && payload.username !== name && name !== 'Player') {
          console.warn(`[Auth] Token username mismatch: token has "${payload.username}", expected "${name}". Retrying...`);
          // Retry once with explicit name
          const retryBody = { uuid: uuid, name: name, scopes: ['hytale:server', 'hytale:client'] };
          if (password) retryBody.password = password;
          const retryResponse = await fetch(`${authServerUrl}/game-session/child`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(retryBody)
          });
          if (retryResponse.ok) {
            const retryData = await retryResponse.json();
            console.log('[Auth] Retry successful');
            return {
              identityToken: retryData.IdentityToken || retryData.identityToken,
              sessionToken: retryData.SessionToken || retryData.sessionToken
            };
          }
        }
      }
    } catch (verifyErr) {
      console.warn('[Auth] Token verification skipped:', verifyErr.message);
    }

    console.log('Auth tokens received from server');
    return { identityToken, sessionToken };
  } catch (error) {
    // Re-throw authentication errors — must not fall back to local tokens
    if (error.passwordRequired || error.lockedOut || error.usernameTaken || error.nameLocked) {
      throw error;
    }
    console.error('Failed to fetch auth tokens:', error.message);
    // Fallback to local generation if server unavailable
    return generateLocalTokens(uuid, name);
  }
}

// Fallback: Generate tokens locally (won't pass signature validation but allows offline testing)
function generateLocalTokens(uuid, name) {
  console.log('Using locally generated tokens (fallback mode)');
  const authServerUrl = getAuthServerUrl();
  const now = Math.floor(Date.now() / 1000);
  const exp = now + 36000;

  const header = Buffer.from(JSON.stringify({
    alg: 'EdDSA',
    kid: '2025-10-01',
    typ: 'JWT'
  })).toString('base64url');

  const identityPayload = Buffer.from(JSON.stringify({
    sub: uuid,
    name: name,
    username: name,
    entitlements: ['game.base'],
    scope: 'hytale:server hytale:client',
    iat: now,
    exp: exp,
    iss: authServerUrl,
    jti: uuidv4()
  })).toString('base64url');

  const sessionPayload = Buffer.from(JSON.stringify({
    sub: uuid,
    scope: 'hytale:server',
    iat: now,
    exp: exp,
    iss: authServerUrl,
    jti: uuidv4()
  })).toString('base64url');

  const signature = crypto.randomBytes(64).toString('base64url');

  return {
    identityToken: `${header}.${identityPayload}.${signature}`,
    sessionToken: `${header}.${sessionPayload}.${signature}`
  };
}

async function launchGame(playerNameOverride = null, progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto', branchOverride = null, options = {}) {
  // ==========================================================================
  // CACHE INVALIDATION: Clear proxyClient module cache to force fresh .env load
  // This prevents stale cached values from affecting multiple launch attempts
  // ==========================================================================
  try {
    const proxyClientPath = require.resolve('../utils/proxyClient');
    if (require.cache[proxyClientPath]) {
      delete require.cache[proxyClientPath];
      console.log('[Launcher] Cleared proxyClient cache for fresh .env load');
    }
  } catch (cacheErr) {
    console.warn('[Launcher] Could not clear proxyClient cache:', cacheErr.message);
  }

  // ==========================================================================
  // STEP 1: Validate player identity FIRST (before any other operations)
  // ==========================================================================
  const launchState = checkLaunchReady();

  // Load username from config - single source of truth
  let playerName = loadUsername();

  if (!playerName) {
    // No username configured - this is a critical error
    const error = new Error('No username configured. Please set your username in Settings before playing.');
    console.error('[Launcher] Launch blocked:', error.message);
    throw error;
  }

  // Allow override only if explicitly provided (for testing/migration)
  if (playerNameOverride && typeof playerNameOverride === 'string' && playerNameOverride.trim()) {
    const overrideName = playerNameOverride.trim();
    if (overrideName !== playerName && overrideName !== 'Player') {
      console.warn(`[Launcher] Username override requested: "${overrideName}" (saved: "${playerName}")`);
      // Use override for this session but DON'T save it - config is source of truth
      playerName = overrideName;
    }
  }

  // Warn if using default 'Player' name (likely misconfiguration)
  if (playerName === 'Player') {
    console.warn('[Launcher] Warning: Using default username "Player". This may cause cosmetic issues.');
  }

  console.log(`[Launcher] Launching game for player: "${playerName}"`);

  // ==========================================================================
  // STEP 2: Synchronize server list
  // ==========================================================================
  try {
    console.log('[Launcher] Synchronizing server list...');
    await syncServerList();
  } catch (syncError) {
    console.warn('[Launcher] Server list sync failed, continuing launch:', syncError.message);
  }

  // ==========================================================================
  // STEP 3: Setup paths and directories
  // ==========================================================================
  const branch = branchOverride || loadVersionBranch();
  const customAppDir = getResolvedAppDir(installPathOverride);
  const customGameDir = path.join(customAppDir, branch, 'package', 'game', 'latest');
  const customJreDir = path.join(customAppDir, branch, 'package', 'jre', 'latest');

  // NEW 2.2.0: Use centralized UserData location
  const userDataDir = getUserDataPath();

  const gameLatest = customGameDir;
  let clientPath = findClientPath(gameLatest);

  if (!clientPath) {
    throw new Error('Game is not installed. Please install the game first.');
  }

  // NOTE: We do NOT save username here anymore - username is only saved
  // when user explicitly changes it in Settings. This prevents accidental
  // overwrites from race conditions or default values.

  if (installPathOverride) {
    saveInstallPath(installPathOverride);
  }

  const configuredJava = (javaPathOverride !== undefined && javaPathOverride !== null
    ? javaPathOverride
    : loadJavaPath() || '').trim();
  let javaBin = null;

  if (configuredJava) {
    javaBin = await resolveJavaPath(configuredJava);
    if (!javaBin) {
      throw new Error(`Configured Java path not found: ${configuredJava}`);
    }
  } else {
    javaBin = getJavaExec(customJreDir);

    if (!getBundledJavaPath(customJreDir)) {
      const fallback = await detectSystemJava();
      if (fallback) {
        javaBin = fallback;
      } else {
        throw new Error('Java runtime not found. Please install the game first or configure Java path.');
      }
    }
  }

  const uuid = getUuidForUser(playerName);
  console.log(`[Launcher] UUID for "${playerName}": ${uuid} (verify this stays constant across launches)`);

  // Fetch tokens from auth server (with password if provided)
  if (progressCallback) {
    progressCallback('Fetching authentication tokens...', null, null, null, null);
  }
  const launchPassword = options?.password || null;
  const { identityToken, sessionToken } = await fetchAuthTokens(uuid, playerName, launchPassword);

  // Patch client and server binaries to use custom auth server (BEFORE signing on macOS)
  // FORCE patch on every launch to ensure consistency
  const authDomain = getAuthDomain();
  if (clientPatcher) {
    try {
      if (progressCallback) {
        progressCallback('Patching game for custom server...', null, null, null, null);
      }
      console.log(`Force patching game binaries for ${authDomain}...`);

      const patchResult = await clientPatcher.ensureClientPatched(gameLatest, (msg, percent) => {
        // console.log(`[Patcher] ${msg}`);
        if (progressCallback && msg) {
          progressCallback(msg, percent, null, null, null);
        }
       }, null, branch);

      if (patchResult.success) {
        console.log(`Game patched successfully (${patchResult.patchCount} total occurrences)`);
        if (patchResult.client) {
          console.log(`  Client: ${patchResult.client.patchCount || 0} occurrences`);
        }
        if (patchResult.agent) {
          console.log(`  Agent: ${patchResult.agent.alreadyExists ? 'already present' : patchResult.agent.success ? 'downloaded' : 'failed'}`);
        }
      } else {
        console.warn('Game patching failed:', patchResult.error);
      }
    } catch (patchError) {
      console.warn('Game patching failed (game may not connect to custom server):', patchError.message);
    }
  }

  // macOS: Sign binaries AFTER patching so the patched binaries have valid signatures
  if (process.platform === 'darwin') {
    try {
      const appBundle = path.join(gameLatest, 'Client', 'Hytale.app');
      const serverDir = path.join(gameLatest, 'Server');

      const signPath = async (targetPath, deep = false) => {
        await execAsync(`xattr -cr "${targetPath}"`).catch(() => { });
        const deepFlag = deep ? '--deep ' : '';
        await execAsync(`codesign --force ${deepFlag}--sign - "${targetPath}"`).catch(() => { });
      };

      if (fs.existsSync(appBundle)) {
        await signPath(appBundle, true);
        console.log('Signed macOS app bundle (after patching)');
      } else {
        await signPath(path.dirname(clientPath), true);
        console.log('Signed macOS client binary (after patching)');
      }

      if (javaBin && fs.existsSync(javaBin)) {
        let jreRoot = path.dirname(path.dirname(javaBin));
        if (jreRoot.endsWith('Home')) {
          jreRoot = path.dirname(path.dirname(jreRoot));
        }
        await signPath(jreRoot, true);
        await signPath(javaBin, false);
        console.log('Signed Java runtime');
      }

      if (fs.existsSync(serverDir)) {
        await execAsync(`xattr -cr "${serverDir}"`).catch(() => { });
        await execAsync(`find "${serverDir}" -type f -perm +111 -exec codesign --force --sign - {} \\;`).catch(() => { });
        console.log('Signed server binaries (after patching)');
      }

      // Create java wrapper (must be signed on macOS)
      if (javaBin && fs.existsSync(javaBin)) {
        const javaWrapperPath = path.join(path.dirname(javaBin), 'java-wrapper');
        const wrapperScript = generateWrapperScript(loadWrapperConfig(), 'darwin', javaBin);
        fs.writeFileSync(javaWrapperPath, wrapperScript, { mode: 0o755 });
        await signPath(javaWrapperPath, false);
        console.log('Created java wrapper from config template');
        javaBin = javaWrapperPath;
      }
    } catch (signError) {
      console.log('Notice: macOS signing step failed:', signError.message);
      console.log('The game may still launch if Gatekeeper allows it');
    }
  }

  // Windows: Create java wrapper to strip/inject JVM flags per wrapper config
  if (process.platform === 'win32' && javaBin && fs.existsSync(javaBin)) {
    try {
      const javaDir = path.dirname(javaBin);
      const javaOriginal = path.join(javaDir, 'java-original.exe');
      const javaWrapperPath = path.join(javaDir, 'java-wrapper.bat');

      if (!fs.existsSync(javaOriginal)) {
        fs.copyFileSync(javaBin, javaOriginal);
        console.log('Backed up java.exe as java-original.exe');
      }

      const wrapperScript = generateWrapperScript(loadWrapperConfig(), 'win32', null);
      fs.writeFileSync(javaWrapperPath, wrapperScript);
      console.log('Created Windows java wrapper from config template');
      javaBin = javaWrapperPath;
    } catch (wrapperError) {
      console.log('Notice: Windows java wrapper creation failed:', wrapperError.message);
    }
  }

  // Linux: Create java wrapper to strip/inject JVM flags per wrapper config
  if (process.platform === 'linux' && javaBin && fs.existsSync(javaBin)) {
    try {
      const javaWrapperPath = path.join(path.dirname(javaBin), 'java-wrapper');
      const wrapperScript = generateWrapperScript(loadWrapperConfig(), 'linux', javaBin);
      fs.writeFileSync(javaWrapperPath, wrapperScript, { mode: 0o755 });
      console.log('Created Linux java wrapper from config template');
      javaBin = javaWrapperPath;
    } catch (wrapperError) {
      console.log('Notice: Linux java wrapper creation failed:', wrapperError.message);
    }
  }

  const args = [
    '--app-dir', gameLatest,
    '--java-exec', javaBin,
    '--auth-mode', 'authenticated',
    '--uuid', uuid,
    '--name', playerName,
    '--identity-token', identityToken,
    '--session-token', sessionToken,
    '--user-dir', userDataDir
  ];

  if (progressCallback) {
    progressCallback('Starting game...', null, null, null, null);
  }

  // Ensure mods are synced for the active profile before launching
  try {
    console.log('Syncing mods for active profile before launch...');
    if (progressCallback) progressCallback('Syncing mods...', null, null, null, null);
    await syncModsForCurrentProfile();
  } catch (syncError) {
    console.error('Failed to sync mods before launch:', syncError);
    // Continue anyway? Or fail? 
    // Warn user but continue might be safer to avoid blocking play if sync is just glitchy
  }

  console.log('Starting game...');
  console.log(`Command: "${clientPath}" ${args.join(' ')}`);

  const env = { ...process.env };

  // Linux: Add Client directory to LD_LIBRARY_PATH so the dynamic linker can find
  // bundled native libraries (e.g. libSDL3_image.so.0). The .NET DllImport only tries
  // bare names like "SDL3_image.so" which don't match versioned .so.0 files.
  // LD_LIBRARY_PATH lets dlopen() find them via standard library resolution.
  if (process.platform === 'linux') {
    const clientDir = path.dirname(clientPath);
    const existing = env.LD_LIBRARY_PATH || '';
    env.LD_LIBRARY_PATH = existing ? `${clientDir}:${existing}` : clientDir;
    console.log(`Linux: LD_LIBRARY_PATH includes ${clientDir}`);
  }

  const waylandEnv = setupWaylandEnvironment();
  Object.assign(env, waylandEnv);
  const gpuEnv = setupGpuEnvironment(gpuPreference);
  Object.assign(env, gpuEnv);
  // Linux: Replace bundled libzstd.so with system version to fix glibc 2.41+ crash
  // The bundled libzstd causes "free(): invalid pointer" on Steam Deck / Ubuntu LTS
  if (process.platform === 'linux' && process.env.HYTALE_NO_LIBZSTD_FIX !== '1') {
    const clientDir = path.dirname(clientPath);
    const bundledLibzstd = path.join(clientDir, 'libzstd.so');
    const backupLibzstd = path.join(clientDir, 'libzstd.so.bundled');

    // Common system libzstd paths
    const systemLibzstdPaths = [
      '/usr/lib64/libzstd.so.1',                  // Fedora/RHEL
      '/usr/lib/libzstd.so.1',                    // Arch Linux, Steam Deck
      '/usr/lib/x86_64-linux-gnu/libzstd.so.1'    // Debian/Ubuntu
    ];

    let systemLibzstd = null;
    for (const p of systemLibzstdPaths) {
      if (fs.existsSync(p)) {
        systemLibzstd = p;
        break;
      }
    }

    if (systemLibzstd && fs.existsSync(bundledLibzstd)) {
      try {
        const stats = fs.lstatSync(bundledLibzstd);

        // Only replace if it's not already a symlink to system version
        if (!stats.isSymbolicLink()) {
          // Backup bundled version
          if (!fs.existsSync(backupLibzstd)) {
            fs.renameSync(bundledLibzstd, backupLibzstd);
            console.log(`Linux: Backed up bundled libzstd.so`);
          } else {
            fs.unlinkSync(bundledLibzstd);
          }

          // Create symlink to system version
          fs.symlinkSync(systemLibzstd, bundledLibzstd);
          console.log(`Linux: Linked libzstd.so to system version (${systemLibzstd}) for glibc 2.41+ compatibility`);
        } else {
          const linkTarget = fs.readlinkSync(bundledLibzstd);
          console.log(`Linux: libzstd.so already linked to ${linkTarget}`);
        }
      } catch (libzstdError) {
        console.warn(`Linux: Could not replace libzstd.so: ${libzstdError.message}`);
      }
    }
  }

  // Kill any stalled game processes from a previous launch to prevent file locks
  // and "game already running" issues (skip if multi-instance mode is enabled)
  if (!loadAllowMultiInstance()) {
    await killGameProcesses();
  }

  // Remove AOT cache: generated by official Hytale JRE, incompatible with F2P JRE.
  // Client adds -XX:AOTCache when this file exists, causing classloading failures.
  const aotCache = path.join(gameLatest, 'Server', 'HytaleServer.aot');
  if (fs.existsSync(aotCache)) {
    try {
      fs.unlinkSync(aotCache);
      console.log('Removed incompatible AOT cache (HytaleServer.aot)');
    } catch (aotErr) {
      console.warn('Could not remove AOT cache:', aotErr.message);
    }
  }

  // DualAuth Agent: Set JAVA_TOOL_OPTIONS so java picks up -javaagent: flag
  // This enables runtime auth patching without modifying the server JAR
  const agentJar = path.join(gameLatest, 'Server', 'dualauth-agent.jar');
  if (fs.existsSync(agentJar)) {
    const agentFlag = `-javaagent:"${agentJar}"`;
    env.JAVA_TOOL_OPTIONS = env.JAVA_TOOL_OPTIONS
      ? `${env.JAVA_TOOL_OPTIONS} ${agentFlag}`
      : agentFlag;
    console.log('DualAuth Agent: enabled via JAVA_TOOL_OPTIONS');
  }

  try {
    let spawnOptions = {
      stdio: ['ignore', 'pipe', 'pipe'],
      detached: true,
      env: env
    };

    if (process.platform === 'win32') {
      spawnOptions.shell = false;
      spawnOptions.windowsHide = true;
    }

    const child = spawn(clientPath, args, spawnOptions);

    // Release process reference immediately so it's truly independent
    // This works on all platforms (Windows, macOS, Linux)
    child.unref();

    console.log(`Game process started with PID: ${child.pid}`);

    let hasExited = false;
    let outputReceived = false;
    let launchCheckTimeout;

    if (child.stdout) {
      child.stdout.on('data', (data) => {
        outputReceived = true;
        const msg = data.toString().trim();
        console.log(`Game output: ${msg}`);
      });
    }

    if (child.stderr) {
      child.stderr.on('data', (data) => {
        outputReceived = true;
        const msg = data.toString().trim();
        console.error(`Game error: ${msg}`);
      });
    }

    child.on('error', (error) => {
      hasExited = true;
      clearTimeout(launchCheckTimeout);
      console.error(`Failed to start game process: ${error.message}`);
      if (progressCallback) {
        progressCallback(`Failed to start game: ${error.message}`, -1, null, null, null);
      }
    });

    child.on('exit', (code, signal) => {
      hasExited = true;
      clearTimeout(launchCheckTimeout);
      
      if (code !== null) {
        console.log(`Game process exited with code ${code}`);
        if (code !== 0) {
          console.error(`[Launcher] Game crashed or exited with error code ${code}`);
          if (progressCallback) {
            progressCallback(`Game exited with error code ${code}`, -1, null, null, null);
          }
        }
      } else if (signal) {
        console.log(`Game process terminated by signal ${signal}`);
      }
    });

    // Process is detached and unref'd - it runs independently from the launcher
    // We cannot reliably detect if the game window actually appears from here,
    // so we report success after spawning. stdout/stderr logging above provides debugging info.
    console.log('Game process spawned and detached successfully');
    if (progressCallback) {
      progressCallback('Game launched successfully', 100, null, null, null);
    }

    // Return immediately after spawn
    return { success: true, installed: true, launched: true, pid: child.pid };
  } catch (spawnError) {
    console.error(`Error spawning game process: ${spawnError.message}`);
    if (progressCallback) {
      progressCallback(`Error launching game: ${spawnError.message}`, -1, null, null, null);
    }
    throw spawnError;
  }
}

async function launchGameWithVersionCheck(playerNameOverride = null, progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto', branchOverride = null, options = {}) {
  try {
    // ==========================================================================
    // PRE-LAUNCH VALIDATION: Check username is configured
    // ==========================================================================
    const launchState = checkLaunchReady();

    if (!launchState.hasUsername) {
      const error = 'No username configured. Please set your username in Settings before playing.';
      console.error('[Launcher] Launch blocked:', error);
      if (progressCallback) {
        progressCallback(error, -1, null, null, null);
      }
      return { success: false, error: error, needsUsername: true };
    }

    console.log(`[Launcher] Pre-launch check passed. Username: "${launchState.username}"`);

    const branch = branchOverride || loadVersionBranch();

    if (progressCallback) {
      progressCallback('Checking for updates...', 0, null, null, null);
    }

    const installedVersion = loadVersionClient();
    const latestVersion = await getLatestClientVersion(branch);

    console.log(`Installed version: ${installedVersion}, Latest version: ${latestVersion} (branch: ${branch})`);

    let needsUpdate = false;
    if (!installedVersion || installedVersion !== latestVersion) {
      needsUpdate = true;
      console.log('Version mismatch or not installed, update required');
    }

    if (needsUpdate) {
      if (progressCallback) {
        progressCallback('Game update required, starting update process...', 10, null, null, null);
      }

      const customAppDir = getResolvedAppDir(installPathOverride);
      const customGameDir = path.join(customAppDir, branch, 'package', 'game', 'latest');
      const customToolsDir = path.join(customAppDir, 'butler');
      const customCacheDir = path.join(customAppDir, 'cache');

      try {
        let versionToInstall = latestVersion;
        if (FORCE_CLEAN_INSTALL_VERSION && !installedVersion) {
          versionToInstall = CLEAN_INSTALL_TEST_VERSION;
          console.log(`TESTING MODE: Clean install detected, forcing version ${versionToInstall} instead of ${latestVersion}`);
        }
        
        await ensureGameInstalled(versionToInstall, branch, progressCallback, customGameDir, customCacheDir, customToolsDir);
        console.log('Game updated successfully, patching will be forced on launch...');

        if (progressCallback) {
          progressCallback('Preparing game launch...', 90, null, null, null);
        }
        await new Promise(resolve => setTimeout(resolve, 3000));

      } catch (updateError) {
        console.error('Update failed:', updateError);
        if (progressCallback) {
          progressCallback(`Update failed: ${updateError.message}`, -1, null, null, null);
        }
        throw updateError;
      }
    }

    if (progressCallback) {
      progressCallback('Launching game...', 80, null, null, null);
    }

    const launchResult = await launchGame(playerNameOverride, progressCallback, javaPathOverride, installPathOverride, gpuPreference, branch, options);
    
    // Ensure we always return a result
    if (!launchResult) {
      console.error('launchGame returned null/undefined, creating fallback response');
      return { success: false, error: 'Game launch failed - no response from launcher' };
    }
    
    return launchResult;
  } catch (error) {
    console.error('Error in version check and launch:', error);
    if (progressCallback) {
      progressCallback(`Error: ${error.message}`, -1, null, null, null);
    }
    // Re-throw authentication errors so IPC handler can return proper flags
    if (error.passwordRequired || error.lockedOut || error.usernameTaken || error.nameLocked) {
      throw error;
    }
    // Always return an error response instead of throwing
    return { success: false, error: error.message || 'Unknown launch error' };
  }
}

module.exports = {
  launchGame,
  launchGameWithVersionCheck
};