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

import type { SyncServer } from "../../../server/local-first/sync-server";
import { FORUM_DOC_ID, type ForumDoc, type StepStatus } from "../schemas";
import { createServer, getServer, deleteServer } from "./hetzner";
import { createDNSRecord, deleteDNSRecord } from "./dns";
import { generateCloudInit, type DiscourseConfig } from "./cloud-init";

function logStep(
	syncServer: SyncServer,
	instanceId: string,
	step: string,
	status: StepStatus,
	message: string,
	metadata: Record<string, unknown> = {},
) {
	const now = Date.now();
	syncServer.changeDoc<ForumDoc>(FORUM_DOC_ID, `log ${step} ${status}`, (d) => {
		if (!d.provisionLogs[instanceId]) d.provisionLogs[instanceId] = [];
		const logs = d.provisionLogs[instanceId];

		if (status === "running") {
			logs.push({
				id: crypto.randomUUID(),
				step,
				status,
				message,
				metadata,
				startedAt: now,
				completedAt: 0,
			});
		} else {
			// Find the running entry for this step and update it
			for (let i = logs.length - 1; i >= 0; i--) {
				if (logs[i].step === step && logs[i].status === "running") {
					logs[i].status = status;
					logs[i].message = message;
					logs[i].metadata = metadata;
					logs[i].completedAt = now;
					break;
				}
			}
		}
	});
}

function updateInstance(
	syncServer: SyncServer,
	instanceId: string,
	fields: Partial<{
		status: string;
		errorMessage: string;
		vpsId: string;
		vpsIp: string;
		dnsRecordId: string;
		sslProvisioned: boolean;
		provisionedAt: number;
		destroyedAt: number;
	}>,
) {
	syncServer.changeDoc<ForumDoc>(FORUM_DOC_ID, `update instance ${instanceId}`, (d) => {
		const inst = d.instances[instanceId];
		if (!inst) return;
		for (const [key, val] of Object.entries(fields)) {
			(inst as any)[key] = val;
		}
		inst.updatedAt = Date.now();
	});
}

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

export async function provisionInstance(syncServer: SyncServer, instanceId: string) {
	const doc = syncServer.getDoc<ForumDoc>(FORUM_DOC_ID);
	const instance = doc?.instances[instanceId];
	if (!instance) throw new Error("Instance not found");

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

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

		// Step 2: Wait for boot
		logStep(syncServer, 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) {
			logStep(syncServer, instanceId, "wait_ready", "error", "VPS failed to boot within 5 minutes");
			updateInstance(syncServer, instanceId, { status: "error", errorMessage: "VPS boot timeout" });
			return;
		}
		logStep(syncServer, instanceId, "wait_ready", "success", "VPS is running");

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

		// Step 4: Wait for Discourse install
		updateInstance(syncServer, instanceId, { status: "installing" });
		logStep(syncServer, 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) {
			logStep(syncServer, instanceId, "install_discourse", "error", "Discourse did not respond within 15 minutes");
			updateInstance(syncServer, instanceId, { status: "error", errorMessage: "Discourse install timeout" });
			return;
		}
		logStep(syncServer, instanceId, "install_discourse", "success", "Discourse is responding");

		// Step 5: Verify live
		updateInstance(syncServer, instanceId, { status: "configuring" });
		logStep(syncServer, 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) {
				updateInstance(syncServer, instanceId, {
					status: "active",
					sslProvisioned: true,
					provisionedAt: Date.now(),
				});
				logStep(syncServer, instanceId, "verify_live", "success", "Forum is live with SSL!");
			} else {
				updateInstance(syncServer, instanceId, { status: "active", provisionedAt: Date.now() });
				logStep(syncServer, instanceId, "verify_live", "success", "Forum is live (SSL pending)");
			}
		} catch {
			updateInstance(syncServer, instanceId, { status: "active", provisionedAt: Date.now() });
			logStep(syncServer, instanceId, "verify_live", "success", "Forum provisioned (SSL may take a few minutes)");
		}
	} catch (e: any) {
		console.error("[Forum] Provisioning error:", e);
		updateInstance(syncServer, instanceId, { status: "error", errorMessage: e.message });
		logStep(syncServer, instanceId, "unknown", "error", e.message);
	}
}

export async function destroyInstance(syncServer: SyncServer, instanceId: string) {
	const doc = syncServer.getDoc<ForumDoc>(FORUM_DOC_ID);
	const instance = doc?.instances[instanceId];
	if (!instance) return;

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

	if (instance.vpsId) {
		await deleteServer(instance.vpsId);
	}
	if (instance.dnsRecordId) {
		await deleteDNSRecord(instance.dnsRecordId);
	}

	updateInstance(syncServer, instanceId, { status: "destroyed", destroyedAt: Date.now() });
}
