import path from 'path';
import os from 'os';

import fs from 'fs-extra';
import Logger from 'electron-log/main';

import Preferences from '~main/modules/preferences';
import Mods from '~main/modules/mods';
import { mainWindow } from '~main/index';
import { isGameRunning } from '~main/modules/updater';
import { patchConfig } from '~main/modules/patcher';
import { applyLocalePatch } from '~main/modules/localePatch';
import { minimizeToTray, restoreFromTray } from '~main/modules/tray';
import { getMod } from '~common/mods';
import {
	ensureWowDesktopEntry,
	gameLaunchEnv,
	spawnDetachedGame,
	spawnWithProton
} from '~main/modules/proton';

import { createTRPCRouter, publicProcedure } from '../trpc';

const chainloaderNeeded = async (clientDir: string): Promise<boolean> => {
	const installed = Mods.status.mods.filter(r => r.installedVersion);
	if (installed.some(r => r.id === 'vanillaFixes')) return true;
	if (installed.some(r => getMod(r.id)?.requires?.includes('vanillaFixes')))
		return true;

	const dllsPath = path.join(clientDir, 'dlls.txt');
	if (await fs.pathExists(dllsPath)) {
		const raw = await fs.readFile(dllsPath, 'utf8');
		return raw.split(/\r?\n/).some(l => l.trim() && !l.trim().startsWith('#'));
	}
	return false;
};

type StartResult = { ok: boolean; error?: string };

export const launcherRouter = createTRPCRouter({
	start: publicProcedure.mutation(async (): Promise<StartResult> => {
		const {
			cleanWdb,
			minimizeToTrayOnPlay,
			clientDir,
			useProton,
			protonPath
		} = Preferences.data;
		if (!clientDir) return { ok: false, error: 'No game folder is set.' };

		const exePath = path.join(clientDir, 'WoW.exe');
		if (!(await fs.pathExists(exePath)))
			return { ok: false, error: 'WoW.exe was not found in the game folder.' };
		if (await isGameRunning(exePath))
			return { ok: false, error: 'WoW is already running.' };

		if (cleanWdb) {
			Logger.log('Cleaning up WDB...');
			await fs.remove(path.join(clientDir, 'WDB'));
		}

		Logger.log('Checking Config.wtf...');
		await patchConfig();

		Logger.log('Applying UI language...');
		await applyLocalePatch(clientDir, Preferences.data.locale);

		const loaderPath = path.join(clientDir, 'VanillaFixes.exe');
		const needsLoader = await chainloaderNeeded(clientDir);
		const useLoader = needsLoader && (await fs.pathExists(loaderPath));
		if (needsLoader && !useLoader)
			Logger.warn(
				'VanillaFixes.exe is missing but mods/dlls.txt expect a chainloader; ' +
					'launching WoW.exe directly (mods will not load).'
			);

		const octoLocale = Preferences.data.locale || 'enUS';
		const gameEnv = { ...process.env, OCTO_LOCALE: octoLocale };
		const launchExe = useLoader ? loaderPath : exePath;
		const launchArgs = useLoader ? ['WoW.exe'] : [];
		const wantProton = os.platform() === 'linux' && !!useProton;

		if (wantProton && !protonPath)
			return {
				ok: false,
				error: 'Proton is enabled but no Proton version is selected.'
			};

		Logger.log(
			wantProton
				? `Launching via Proton${useLoader ? ' + VanillaFixes' : ''} (OCTO_LOCALE=${octoLocale})...`
				: useLoader
				? `Launching via VanillaFixes (OCTO_LOCALE=${octoLocale})...`
				: `Launching ${exePath} (OCTO_LOCALE=${octoLocale})...`
		);

		const launchEnv = gameLaunchEnv(gameEnv);
		if (os.platform() === 'linux')
			await ensureWowDesktopEntry(clientDir).catch(e =>
				Logger.warn('Failed to write WoW desktop entry', e)
			);

		let child;
		try {
			child = wantProton
				? await spawnWithProton({
						protonDir: protonPath!,
						exePath: launchExe,
						args: launchArgs,
						cwd: clientDir,
						env: launchEnv
				  })
				: spawnDetachedGame(launchExe, launchArgs, {
						env: launchEnv,
						cwd: clientDir
				  });
		} catch (e) {
			Logger.error('Failed to launch the game', e);
			const message = e instanceof Error ? e.message : String(e);
			return { ok: false, error: `Failed to launch the game: ${message}` };
		}

		try {
			await new Promise<void>((resolve, reject) => {
				child.once('spawn', resolve);
				child.once('error', reject);
			});
		} catch (e) {
			Logger.error('Failed to launch the game', e);
			const message = e instanceof Error ? e.message : String(e);
			return { ok: false, error: `Failed to launch the game: ${message}` };
		}

		child.on('error', e => Logger.error('Game process error', e));
		// VanillaFixes / Proton often exit right after spawning WoW — don't treat
		// that as the game ending. Tray restore uses isGameRunning polling.
		child.on('exit', code => {
			Logger.log(
				`Launch helper exited (code=${code}); tracking WoW.exe separately`
			);
		});

		if (!minimizeToTrayOnPlay) {
			mainWindow?.close();
			return { ok: true };
		}

		minimizeToTray();
		void watchUntilGameExits(exePath);
		return { ok: true };
	})
});

const sleep = (ms: number) => new Promise<void>(r => setTimeout(r, ms));

/** Wait for WoW.exe to appear (chainloader/proton lag), then restore tray on exit. */
const watchUntilGameExits = async (exePath: string) => {
	const appearDeadline = Date.now() + 45_000;
	let seen = false;
	while (Date.now() < appearDeadline) {
		if (await isGameRunning(exePath)) {
			seen = true;
			break;
		}
		await sleep(500);
	}
	if (!seen) {
		Logger.warn('WoW.exe never appeared after launch; restoring tray');
		restoreFromTray();
		return;
	}

	Logger.log('WoW.exe detected; waiting for it to exit...');
	while (await isGameRunning(exePath)) {
		await sleep(2000);
	}
	Logger.log('WoW stopped');
	restoreFromTray();
};
