import { existsSync, mkdirSync, openSync, rmSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';

export async function ensureWebBuilt(): Promise<void> {
	const buildEntry = join('apps', 'web', 'build', 'index.js');
	const isTest = String(process.env.NODE_ENV || '').toLowerCase() === 'test';
	if (existsSync(buildEntry) && !isTest) return;

	const lockFile = join('apps', 'web', '.build.lock');

	let haveLock = false;
	try {
		// Intentar crear lock (exclusivo). Si existe, esperar a que termine la otra build.
		openSync(lockFile, 'wx');
		haveLock = true;
	} catch {
		// Otra build en progreso o ya hecha. Esperar hasta que exista el build.
		const timeoutMs = 60_000;
		const start = Date.now();
		while (!existsSync(buildEntry)) {
			if (Date.now() - start > timeoutMs) {
				throw new Error('Timeout esperando a que termine el build de apps/web');
			}
			// Dormir 100ms
			await new Promise((res) => setTimeout(res, 100));
		}
		return;
	}

	try {
		// Asegurar carpeta build
		try {
			mkdirSync(dirname(buildEntry), { recursive: true });
		} catch {}

		// Ejecutar "bun run build" dentro de apps/web
		const proc = Bun.spawn({
			cmd: [process.execPath, 'run', 'build'],
			cwd: join('apps', 'web'),
			stdout: 'inherit',
			stderr: 'inherit',
			env: {
				...process.env,
				NODE_ENV: 'production'
			}
		});
		const exitCode = await proc.exited;
		if (exitCode !== 0) {
			throw new Error(`Fallo al construir apps/web (exit ${exitCode})`);
		}
	} finally {
		// Liberar lock
		try {
			rmSync(lockFile, { force: true });
		} catch {}
	}
}

export type RunningServer = {
	baseUrl: string;
	stop: () => Promise<void>;
	pid: number | null;
	port: number;
};

export async function startWebServer(opts: {
	port?: number;
	env?: Record<string, string>;
} = {}): Promise<RunningServer> {
	await ensureWebBuilt();

	const port = Number(opts.port || 19080);

	// Lanzar servidor Node adapter: "bun ./build/index.js" en apps/web
	const child = Bun.spawn({
		cmd: [process.execPath, './build/index.js'],
		cwd: join('apps', 'web'),
		stdout: 'pipe',
		stderr: 'pipe',
		env: {
			...process.env,
			PORT: String(port),
			NODE_ENV: 'test',
			...(opts.env || {})
		}
	});

	// Esperar a que esté arriba (ping a "/")
	const baseUrl = `http://127.0.0.1:${port}`;
	const startedAt = Date.now();
	const timeoutMs = 30_000;
	let lastErr: any = null;

	while (Date.now() - startedAt < timeoutMs) {
		try {
			const res = await fetch(baseUrl + '/', { method: 'GET' });
			if (res) break;
		} catch (e) {
			lastErr = e;
		}
		await new Promise((res) => setTimeout(res, 100));
	}

	if (Date.now() - startedAt >= timeoutMs) {
		try { child.kill(); } catch {}
		throw new Error(`Timeout esperando al servidor web: ${lastErr?.message || lastErr}`);
	}

	// Conectar logs a consola (opcional)
	(async () => {
		try {
			for await (const chunk of child.stdout) {
				try {
					process.stderr.write(`[web] ${new TextDecoder().decode(chunk)}`);
				} catch {}
			}
		} catch {}
	})();
	(async () => {
		try {
			for await (const chunk of child.stderr) {
				try {
					process.stderr.write(`[web] ${new TextDecoder().decode(chunk)}`);
				} catch {}
			}
		} catch {}
	})();

	return {
		baseUrl,
		port,
		pid: child.pid,
		stop: async () => {
			try { child.kill(); } catch {}
			// Pequeña espera para liberar puerto
			await new Promise((res) => setTimeout(res, 50));
		}
	};
}
