import path from 'path';
import { spawn } from 'child_process';

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 Updater, { isGameRunning } from '~main/modules/updater';
import {
	patchConfig,
	patchExecutable,
	ensureDxvkConf
} from '~main/modules/patcher';
import { removeLegacyLocalePatches } from '~main/modules/localePatch';
import { syncVanillaFixesCache } from '~main/modules/dllsTxt';
import { stopSeeding } from '~main/modules/aria2';
import { minimizeToTray, restoreFromTray } from '~main/modules/tray';
import { getMod } from '~common/mods';

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 };

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

let starting = false;

export const launcherRouter = createTRPCRouter({
	start: publicProcedure.mutation(async (): Promise<StartResult> => {
		if (starting) return { ok: false, error: 'The game is already launching.' };
		starting = true;
		try {
			const { cleanWdb, minimizeToTrayOnPlay, clientDir } = 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 (Mods.status.dirty)
				return {
					ok: false,
					error: 'You have unapplied mod changes. Click Apply first.'
				};

			stopSeeding();

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

			Logger.log('Syncing preferred monitor...');
			await Mods.verify();

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

			await removeLegacyLocalePatches(clientDir);

			if (Preferences.data.patchedLocale !== Preferences.data.locale) {
				Logger.log(
					`Applying the client language (${Preferences.data.locale})...`
				);
				try {
					await patchExecutable();
					await patchConfig(true);
					await Updater.recordPatchedWow();
					if (!cleanWdb)
						await fs.remove(path.join(clientDir, 'WDB')).catch(() => {});
				} catch (e) {
					Logger.error(
						'Could not apply the client language; launching with the previous one',
						e
					);
				}
			}

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

			Logger.log(
				useLoader ? 'Launching via VanillaFixes...' : `Launching ${exePath}...`
			);
			const child = useLoader
				? spawn(loaderPath, ['WoW.exe'], {
						cwd: clientDir,
						detached: !minimizeToTrayOnPlay
				  })
				: spawn(exePath, {
						cwd: clientDir,
						detached: !minimizeToTrayOnPlay
				  });

			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));

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

			minimizeToTray();
			if (useLoader) {
				void (async () => {
					try {
						const started = Date.now();
						while (
							Date.now() - started < 30_000 &&
							!(await isGameRunning(exePath))
						)
							await delay(1000);
						while (await isGameRunning(exePath)) await delay(3000);
					} finally {
						Logger.log('WoW stopped');
						restoreFromTray();
					}
				})();
			} else {
				child.on('exit', () => {
					Logger.log('WoW stopped');
					restoreFromTray();
				});
			}
			return { ok: true };
		} catch (e) {
			Logger.error('Failed to start the game', e);
			return { ok: false, error: e instanceof Error ? e.message : String(e) };
		} finally {
			starting = false;
		}
	})
});
