/**
 * Forum instance provisioner — async pipeline that creates a VPS,
 * configures DNS, installs Discourse, and verifies it's live.
 */

import { sql } from "../../../shared/db/pool";
import { createServer, getServer, deleteServer } from "./hetzner";
import { createDNSRecord, deleteDNSRecord } from "./dns";
import { generateCloudInit, type DiscourseConfig } from "./cloud-init";

type StepStatus = "running" | "success" | "error" | "skipped";

async function logStep(
	instanceId: string,
	step: string,
	status: StepStatus,
	message: string,
	metadata: Record<string, unknown> = {},
) {
	if (status === "running") {
		await sql.unsafe(
			`INSERT INTO rforum.provision_logs (instance_id, step, status, message, metadata)
			 VALUES ($1, $2, $3, $4, $5::jsonb)`,
			[instanceId, step, status, message, JSON.stringify(metadata)],
		);
	} else {
		await sql.unsafe(
			`UPDATE rforum.provision_logs SET status = $1, message = $2, metadata = $3::jsonb, completed_at = NOW()
			 WHERE instance_id = $4 AND step = $5 AND status = 'running'`,
			[status, message, JSON.stringify(metadata), instanceId, step],
		);
	}
}

async function updateInstance(instanceId: string, fields: Record<string, unknown>) {
	const sets: string[] = [];
	const params: any[] = [];
	let idx = 1;
	for (const [key, val] of Object.entries(fields)) {
		sets.push(`${key} = $${idx}`);
		params.push(val);
		idx++;
	}
	sets.push("updated_at = NOW()");
	params.push(instanceId);
	await sql.unsafe(`UPDATE rforum.instances SET ${sets.join(", ")} WHERE id = $${idx}`, params);
}

async function sleep(ms: number) {
	return new Promise((r) => setTimeout(r, ms));
}

export async function provisionInstance(instanceId: string) {
	const [instance] = await sql.unsafe("SELECT * FROM rforum.instances WHERE id = $1", [instanceId]);
	if (!instance) throw new Error("Instance not found");

	await updateInstance(instanceId, { status: "provisioning" });

	try {
		// Step 1: Create VPS
		await logStep(instanceId, "create_vps", "running", "Creating VPS...");
		const config: DiscourseConfig = {
			hostname: instance.domain,
			adminEmail: instance.admin_email,
			...(instance.smtp_config?.host ? {
				smtpHost: instance.smtp_config.host,
				smtpPort: instance.smtp_config.port,
				smtpUser: instance.smtp_config.user,
				smtpPassword: instance.smtp_config.password,
			} : {}),
		};
		const userData = generateCloudInit(config);
		const { serverId, ip } = await createServer({
			name: `discourse-${instance.domain.replace(/\./g, "-")}`,
			serverType: instance.size,
			region: instance.region,
			userData,
		});
		await updateInstance(instanceId, { vps_id: serverId, vps_ip: ip });
		await logStep(instanceId, "create_vps", "success", `VPS created: ${ip}`, { serverId, ip });

		// Step 2: Wait for boot
		await logStep(instanceId, "wait_ready", "running", "Waiting for VPS to boot...");
		let booted = false;
		for (let i = 0; i < 60; i++) {
			await sleep(5000);
			const server = await getServer(serverId);
			if (server?.status === "running") {
				booted = true;
				break;
			}
		}
		if (!booted) {
			await logStep(instanceId, "wait_ready", "error", "VPS failed to boot within 5 minutes");
			await updateInstance(instanceId, { status: "error", error_message: "VPS boot timeout" });
			return;
		}
		await logStep(instanceId, "wait_ready", "success", "VPS is running");

		// Step 3: Configure DNS
		await logStep(instanceId, "configure_dns", "running", "Configuring DNS...");
		const subdomain = instance.domain.replace(".rforum.online", "");
		const dns = await createDNSRecord(subdomain, ip);
		if (dns) {
			await updateInstance(instanceId, { dns_record_id: dns.recordId });
			await logStep(instanceId, "configure_dns", "success", `DNS record created for ${instance.domain}`);
		} else {
			await logStep(instanceId, "configure_dns", "skipped", "DNS configuration skipped — configure manually");
		}

		// Step 4: Wait for Discourse install
		await updateInstance(instanceId, { status: "installing" });
		await logStep(instanceId, "install_discourse", "running", "Installing Discourse (this takes 10-15 minutes)...");
		let installed = false;
		for (let i = 0; i < 60; i++) {
			await sleep(15000);
			try {
				const res = await fetch(`http://${ip}`, { redirect: "manual" });
				if (res.status === 200 || res.status === 302) {
					installed = true;
					break;
				}
			} catch {}
		}
		if (!installed) {
			await logStep(instanceId, "install_discourse", "error", "Discourse did not respond within 15 minutes");
			await updateInstance(instanceId, { status: "error", error_message: "Discourse install timeout" });
			return;
		}
		await logStep(instanceId, "install_discourse", "success", "Discourse is responding");

		// Step 5: Verify live
		await updateInstance(instanceId, { status: "configuring" });
		await logStep(instanceId, "verify_live", "running", "Verifying Discourse is live...");
		try {
			const res = await fetch(`https://${instance.domain}`, { redirect: "manual" });
			if (res.status === 200 || res.status === 302) {
				await updateInstance(instanceId, {
					status: "active",
					ssl_provisioned: true,
					provisioned_at: new Date().toISOString(),
				});
				await logStep(instanceId, "verify_live", "success", "Forum is live with SSL!");
			} else {
				await updateInstance(instanceId, { status: "active", provisioned_at: new Date().toISOString() });
				await logStep(instanceId, "verify_live", "success", "Forum is live (SSL pending)");
			}
		} catch {
			await updateInstance(instanceId, { status: "active", provisioned_at: new Date().toISOString() });
			await logStep(instanceId, "verify_live", "success", "Forum provisioned (SSL may take a few minutes)");
		}
	} catch (e: any) {
		console.error("[Forum] Provisioning error:", e);
		await updateInstance(instanceId, { status: "error", error_message: e.message });
		await logStep(instanceId, "unknown", "error", e.message);
	}
}

export async function destroyInstance(instanceId: string) {
	const [instance] = await sql.unsafe("SELECT * FROM rforum.instances WHERE id = $1", [instanceId]);
	if (!instance) return;

	await updateInstance(instanceId, { status: "destroying" });

	if (instance.vps_id) {
		await deleteServer(instance.vps_id);
	}
	if (instance.dns_record_id) {
		await deleteDNSRecord(instance.dns_record_id);
	}

	await updateInstance(instanceId, { status: "destroyed", destroyed_at: new Date().toISOString() });
}
