import { spawn } from "node:child_process";
import { mkdir } from "node:fs/promises";
import { join } from "node:path";

import { env } from "./config.ts";
import type { Device } from "./types.ts";

export interface RunOptions {
	id: string;
	url: string;
	device: Device;
	iterations: number;
}

export interface RunResult {
	outputDir: string;
	stdout: string;
	stderr: string;
	exitCode: number;
}

/**
 * Spawn the sitespeed.io CLI for a single URL run. Returns when the
 * process exits — does NOT throw on non-zero exit; the caller decides
 * whether the partial output is usable.
 *
 * Output layout (after run):
 *   <RESULTS_DIR>/<id>/
 *     ├── data/browsertime.har
 *     ├── data/browsertime/pages/.../browsertime.json
 *     ├── pages/.../lighthouse.report.html
 *     ├── pages/.../lighthouse.report.json (when --plugins.add lighthouse)
 *     └── screenshots/*.png
 */
export async function runSitespeed(opts: RunOptions): Promise<RunResult> {
	const outputDir = join(env.RESULTS_DIR, opts.id);
	await mkdir(outputDir, { recursive: true });

	const args = buildArgs(opts, outputDir);

	return new Promise<RunResult>((resolve) => {
		const child = spawn(env.SITESPEED_BIN, args, {
			cwd: outputDir,
			env: { ...process.env },
		});

		let stdout = "";
		let stderr = "";

		child.stdout.on("data", (chunk: Buffer) => {
			stdout += chunk.toString();
		});
		child.stderr.on("data", (chunk: Buffer) => {
			stderr += chunk.toString();
		});

		child.on("error", (err) => {
			resolve({
				outputDir,
				stdout,
				stderr: `${stderr}\n[spawn error] ${err.message}`,
				exitCode: -1,
			});
		});

		child.on("close", (code) => {
			resolve({
				outputDir,
				stdout,
				stderr,
				exitCode: code ?? -1,
			});
		});
	});
}

function buildArgs(opts: RunOptions, outputDir: string): string[] {
	const args: string[] = [
		opts.url,
		"--outputFolder",
		outputDir,
		"--browser",
		"chrome",
		"--browsertime.iterations",
		String(opts.iterations),
		// Always headless inside the container; HEADLESS=false is honoured
		// only when running on a host with a display.
		...(env.HEADLESS ? ["--browsertime.headless"] : []),
		// Lighthouse plugin for the lighthouse.report.html + .json.
		// In the official plus1 image it's installed globally, not inside
		// /usr/src/app/node_modules — pass an absolute path so the loader
		// finds it without an extra resolve step.
		"--plugins.add",
		env.LIGHTHOUSE_PLUGIN_PATH,
		// Lighthouse JSON output (otherwise only HTML is written).
		"--lighthouse.output",
		"html,json",
		// analysisstorer dumps every analysis message (browsertime.pageSummary,
		// lighthouse.pageSummary, etc.) as JSON next to the HTML report. Not
		// enabled by default — we need it so the worker can parse results.
		"--plugins.add",
		"analysisstorer",
		// HAR is emitted by browsertime by default; just make sure it's on.
		"--browsertime.har",
		"true",
		// Disable noisy / network-heavy plugins we don't consume.
		"--plugins.disable",
		"crux",
		"--plugins.disable",
		"sustainable",
		// Chrome flags useful inside docker.
		"--browsertime.chrome.args",
		"no-sandbox",
		"--browsertime.chrome.args",
		"disable-dev-shm-usage",
	];

	if (opts.device === "mobile") {
		// Emulate a mid-range mobile device.
		args.push(
			"--mobile",
			"true",
			"--browsertime.viewPort",
			"412x823",
			"--browsertime.connectivity.profile",
			"4g",
		);
	} else {
		args.push("--browsertime.viewPort", "1366x768");
	}

	if (env.EXTRA_SITESPEED_ARGS.trim().length > 0) {
		args.push(...env.EXTRA_SITESPEED_ARGS.trim().split(/\s+/));
	}

	return args;
}
