/*
 * sai-virt - v-libvirt.c
 *
 * Copyright (C) 2019 - 2026 Andy Green <andy@warmcat.com>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation:
 *  version 2.1 of the License.
 */

#include <libwebsockets.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <libvirt/libvirt.h>

#include "v-private.h"

virConnectPtr conn;

static char *
replace_string(const char *orig, const char *rep, const char *with)
{
	char *result;
	char *ins;
	char *tmp;
	size_t len_rep;
	size_t len_with;
	size_t len_front;
	size_t count;

	if (!orig || !rep)
		return NULL;
	len_rep = strlen(rep);
	if (len_rep == 0)
		return NULL;
	if (!with)
		with = "";
	len_with = strlen(with);

	ins = (char *)orig;
	for (count = 0; (tmp = strstr(ins, rep)); ++count) {
		ins = tmp + len_rep;
	}

	tmp = result = malloc(strlen(orig) + (count * len_with) + 1);
	if (!result)
		return NULL;

	while (count--) {
		ins = strstr(orig, rep);
		len_front = lws_ptr_diff_size_t(ins, orig);
		tmp = strncpy(tmp, orig, len_front) + len_front;
		tmp = strcpy(tmp, with) + len_with;
		orig += len_front + len_rep;
	}
	strcpy(tmp, orig);
	return result;
}

static void
strip_xml_tags(char *xml, const char *start_tag, const char *end_tag)
{
	char *start;
	while ((start = strstr(xml, start_tag))) {
		char *end = strstr(start, end_tag);
		if (!end)
			break;
		end += strlen(end_tag);
		memmove(start, end, strlen(end) + 1);
	}
}

static int
ops_libvirt_init(struct sai_virt *virt)
{
	conn = virConnectOpen("qemu:///system");
	if (!conn) {
		lwsl_err("Failed to open connection to qemu:///system\n");
		return 1;
	}
	lwsl_notice("%s: libvirt ops initialized\n", __func__);
	return 0;
}

static int
ops_libvirt_spawn(struct sai_virt *virt, struct saiv_vm *vm)
{
	virDomainPtr dom;
	virStoragePoolPtr pool;
	virStorageVolPtr vol;
	char *xml, *xml2, *xml3;
	char vol_xml[1024];
	char overlay_path[256];
	char orig_name_tag[128];
	char new_name_tag[128];
	char orig_source_tag[256];
	char new_source_tag[256];
	const char *shm_pool_xml = "<pool type='dir'><name>sai_shm</name><target><path>/dev/shm</path></target></pool>";

	lwsl_notice("%s: Spawning ephemeral VM %s for platform: %s (base %s)\n", 
			__func__, vm->name, vm->plat->name, vm->plat->base_image);

	if (!conn)
		return 1;

	/* 1. Ensure the /dev/shm storage pool exists */
	pool = virStoragePoolLookupByName(conn, "sai_shm");
	if (!pool) {
		pool = virStoragePoolCreateXML(conn, shm_pool_xml, 0);
		if (!pool) {
			lwsl_err("Failed to create transient shm storage pool\n");
			return 1;
		}
	} else {
		/* If it exists but is inactive, start it */
		int active = virStoragePoolIsActive(pool);
		if (active == 0)
			virStoragePoolCreate(pool, 0);
	}

	long capacity_size = 20;
	const char *capacity_unit = "G";
	if (vm->plat->overlay_size[0]) {
		char *p;
		capacity_size = strtol(vm->plat->overlay_size, &p, 10);
		if (p && *p)
			capacity_unit = p;
	}

	/* 2. Create the overlay volume using libvirt API */
	lws_snprintf(vol_xml, sizeof(vol_xml),
		"<volume>"
		"  <name>%s.qcow2</name>"
		"  <capacity unit='%s'>%ld</capacity>"
		"  <target><format type='qcow2'/></target>"
		"  <backingStore>"
		"    <path>%s</path>"
		"    <format type='qcow2'/>"
		"  </backingStore>"
		"</volume>",
		vm->name, 
		capacity_unit, capacity_size,
		vm->plat->base_image);

	/* Ensure no stale volume exists */
	char vol_name[128];
	lws_snprintf(vol_name, sizeof(vol_name), "%s.qcow2", vm->name);
	vol = virStorageVolLookupByName(pool, vol_name);
	if (vol) {
		lwsl_notice("Stale storage volume %s found, deleting...\n", vol_name);
		virStorageVolDelete(vol, 0);
		virStorageVolFree(vol);
	}

	vol = virStorageVolCreateXML(pool, vol_xml, 0);
	if (!vol) {
		lwsl_err("Failed to create libvirt storage volume for overlay\n");
		virStoragePoolFree(pool);
		return 1;
	}
	virStorageVolFree(vol);
	virStoragePoolFree(pool);

	lws_snprintf(overlay_path, sizeof(overlay_path), "/dev/shm/%s.qcow2", vm->name);

	/* 3. Get base domain XML and manipulate it */
	dom = virDomainLookupByName(conn, vm->plat->name);
	if (!dom) {
		lwsl_err("Failed to find base domain %s\n", vm->plat->name);
		return 1;
	}

	xml = virDomainGetXMLDesc(dom, 0);
	virDomainFree(dom);

	if (!xml) {
		lwsl_err("Failed to get XML for base domain\n");
		return 1;
	}

	/* Replace <name>base</name> with <name>vm->name</name> */
	lws_snprintf(orig_name_tag, sizeof(orig_name_tag), "<name>%s</name>", vm->plat->name);
	lws_snprintf(new_name_tag, sizeof(new_name_tag), "<name>%s</name>", vm->name);
	xml2 = replace_string(xml, orig_name_tag, new_name_tag);
	free(xml);

	/* Replace <source file='base_image'/> with <source file='overlay_path'/> */
	lws_snprintf(orig_source_tag, sizeof(orig_source_tag), "file='%s'", vm->plat->base_image);
	lws_snprintf(new_source_tag, sizeof(new_source_tag), "file='%s'", overlay_path);
	xml3 = replace_string(xml2, orig_source_tag, new_source_tag);
	free(xml2);

	if (!xml3) {
		lwsl_err("Failed to manipulate XML\n");
		return 1;
	}

	/* Inject qemu namespace into <domain> */
	char *xml4 = replace_string(xml3, "<domain type=", "<domain xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0' type=");
	if (!xml4)
		xml4 = strdup(xml3);
	free(xml3);

	/* Inject smbios mode='sysinfo' into <os> if missing */
	char *xml4a = xml4;
	if (!strstr(xml4, "<smbios mode='sysinfo'/>")) {
		xml4a = replace_string(xml4, "</os>", "    <smbios mode='sysinfo'/>\n  </os>");
		if (xml4a)
			free(xml4);
		else
			xml4a = xml4;
	}

	/* Inject SMBIOS serial number and fw_cfg for builder identity */
	char fw_cfg_tag[512];
	lws_snprintf(fw_cfg_tag, sizeof(fw_cfg_tag), 
		"  <sysinfo type='smbios'>\n"
		"    <system>\n"
		"      <entry name='serial'>sai_builder_id:%s</entry>\n"
		"    </system>\n"
		"  </sysinfo>\n"
		"  <qemu:commandline>\n"
		"    <qemu:arg value='-fw_cfg'/>\n"
		"    <qemu:arg value='name=opt/sai_builder_id,string=%s'/>\n"
		"  </qemu:commandline>\n"
		"</domain>", vm->name, vm->name);

	char *xml5 = replace_string(xml4a, "</domain>", fw_cfg_tag);
	if (xml5) {
		free(xml4a);
	} else {
		xml5 = xml4a;
	}

	if (!xml5) {
		lwsl_err("Failed to manipulate XML\n");
		return 1;
	}

	/* Remove UUID so libvirt generates a new one, avoiding conflicts with the base VM */
	strip_xml_tags(xml5, "<uuid>", "</uuid>");
	/* Remove MAC addresses so libvirt generates new ones, avoiding network conflicts */
	strip_xml_tags(xml5, "<mac address=", "/>");

	/* 4. Boot the transient domain */
	dom = virDomainCreateXML(conn, xml5, 0);
	free(xml5);

	if (!dom) {
		lwsl_err("Failed to create transient domain %s\n", vm->name);
		return 1;
	}

	virDomainFree(dom);
	lwsl_notice("Successfully spawned ephemeral VM %s\n", vm->name);

	return 0;
}

static int
ops_libvirt_destroy(struct sai_virt *virt, struct saiv_vm *vm)
{
	virDomainPtr dom;
	virStoragePoolPtr pool;
	virStorageVolPtr vol;
	char vol_name[128];

	lwsl_notice("%s: Destroying ephemeral VM: %s\n", __func__, vm->name);

	if (!conn)
		return 1;

	dom = virDomainLookupByName(conn, vm->name);
	if (dom) {
		virDomainDestroy(dom);
		virDomainFree(dom);
	} else {
		lwsl_warn("Domain %s not found during destroy\n", vm->name);
	}

	pool = virStoragePoolLookupByName(conn, "sai_shm");
	if (pool) {
		lws_snprintf(vol_name, sizeof(vol_name), "%s.qcow2", vm->name);
		vol = virStorageVolLookupByName(pool, vol_name);
		if (vol) {
			virStorageVolDelete(vol, 0);
			virStorageVolFree(vol);
		} else {
			lwsl_warn("Volume %s not found in pool sai_shm\n", vol_name);
		}
		virStoragePoolFree(pool);
	}

	return 0;
}

const sai_virt_ops_t ops_libvirt = {
	.name = "libvirt",
	.init = ops_libvirt_init,
	.spawn = ops_libvirt_spawn,
	.destroy = ops_libvirt_destroy,
};