//go:build antivm && linux

package core

import (
	"os"
	"path/filepath"
	"strings"
	"syscall"
)

func init() {
	registerLinuxChecks()
}

func registerLinuxChecks() {
	// 100% certainty
	registerCheck("firmware_dmi", firmwareDMI, 100)

	// 95% certainty
	registerCheck("devices_pci", devicesPCI, 95)

	// 80% certainty
	registerCheck("uml_cpu", umlCPU, 80)
	registerCheck("kgt_signature", kgtSignature, 80)

	// 75% certainty
	registerCheck("nsjail_pid", nsjailPID, 75)

	// 70% certainty
	registerCheck("vmware_ioports", vmwareIOPorts, 70)
	registerCheck("cgroup", cgroupVM, 70)
	registerCheck("qemu_fw_cfg", qemuFWCfg, 70)

	// 65% certainty
	registerCheck("dmi_product", dmiProduct, 65)
	registerCheck("dmi_sys_vendor", dmiSysVendor, 65)
	registerCheck("dmi_chassis_vendor", dmiChassisVendor, 65)
	registerCheck("vmware_iomem", vmwareIOMEM, 65)
	registerCheck("vmware_dmesg", vmwareDMESG, 65)

	// 55% certainty
	registerCheck("dmidecode", dmidecodeCheck, 55)
	registerCheck("dmesg", dmesgCheck, 55)

	// 50% certainty
	registerCheck("dmi_scan", dmiScan, 50)
	registerCheck("smbios_vm_bit", smbiosVMBit, 50)
	registerCheck("thread_mismatch_linux", threadMismatchLinux, 50)
	registerCheck("amd_sev_msr", amdSEVMSR, 50)

	// 40% certainty
	registerCheck("vmware_scsi", vmwareSCSI, 40)
	registerCheck("qemu_virtual_dmi", qemuVirtualDMI, 40)
	registerCheck("processes_linux", processesLinux, 40)

	// 35% certainty
	registerCheck("systemd_virt", systemdVirt, 35)
	registerCheck("hwmon", hwmonMissing, 35)

	// 30% certainty
	registerCheck("wsl_proc", wslProc, 30)

	// 20% certainty
	registerCheck("hypervisor_dir", hypervisorDir, 20)
	registerCheck("temperature", temperatureCheck, 20)
	registerCheck("qemu_usb", qemuUSB, 20)
	registerCheck("ctype", dmiChassisType, 20)

	// 15% certainty
	registerCheck("vbox_module", vboxModule, 15)
	registerCheck("sysinfo_proc", sysinfoProc, 15)
	registerCheck("file_access_history", fileAccessHistory, 15)

	// 10% certainty
	registerCheck("linux_user_host", linuxUserHost, 10)

	// 5% certainty
	registerCheck("podman_file", podmanFile, 5)
	registerCheck("bluestacks", bluestacksFolders, 5)
	registerCheck("kmsg", kmsgCheck, 5)
}

// --- Linux 100% ---

func firmwareDMI() bool {
	// Check all DMI entries for VM signatures
	dirs := []string{
		"/sys/class/dmi/id",
		"/sys/devices/virtual/dmi/id",
	}
	vmBrands := []string{"virtualbox", "vmware", "qemu", "kvm", "innotek",
		"xen", "bochs", "hyper-v", "microsoft", "oracle", "oem"}
	for _, d := range dirs {
		entries, err := os.ReadDir(d)
		if err != nil {
			continue
		}
		for _, e := range entries {
			if e.IsDir() {
				continue
			}
			data, err := os.ReadFile(filepath.Join(d, e.Name()))
			if err != nil {
				continue
			}
			content := strings.ToLower(string(data))
			for _, brand := range vmBrands {
				if strings.Contains(content, brand) {
					return true
				}
			}
		}
	}
	return false
}

// --- Linux 95% ---

func devicesPCI() bool {
	entries, err := os.ReadDir("/sys/bus/pci/devices")
	if err != nil {
		return false
	}
	// VM vendors: 8086 (Intel), 15ad (VMware), 80ee (VirtualBox),
	// 1af4 (Red Hat/QEMU), 10de (NVIDIA in passthrough)
	vmVendors := []string{"15ad", "80ee", "1af4"}
	for _, e := range entries {
		vendorFile := filepath.Join("/sys/bus/pci/devices", e.Name(), "vendor")
		data, err := os.ReadFile(vendorFile)
		if err != nil {
			continue
		}
		vendor := strings.TrimSpace(string(data))
		for _, v := range vmVendors {
			if strings.Contains(strings.ToLower(vendor), v) {
				return true
			}
		}
	}
	return false
}

// --- Linux 80% ---

func umlCPU() bool {
	content := readFile("/proc/cpuinfo")
	return strings.Contains(content, "UML")
}

func kgtSignature() bool {
	content := readFile("/proc/cpuinfo")
	return strings.Contains(content, "Intel KGT") || strings.Contains(content, "TGKIntel")
}

// --- Linux 75% ---

func nsjailPID() bool {
	content := readFile("/proc/self/status")
	if content == "" {
		return false
	}
	// nsjail makes processes see PID 1 in the namespace
	for _, line := range strings.Split(content, "\n") {
		if strings.HasPrefix(line, "Pid:") {
			parts := strings.Fields(line)
			if len(parts) >= 2 && parts[1] == "1" {
				// Check if nsjail env vars are present
				if os.Getenv("NSJAIL") != "" {
					return true
				}
				// Check cgroup for nsjail
				cg := readFile("/proc/self/cgroup")
				return strings.Contains(cg, "nsjail")
			}
		}
	}
	return false
}

// --- Linux 70% ---

func vmwareIOPorts() bool {
	return strings.Contains(readFile("/proc/ioports"), "VMware")
}

func cgroupVM() bool {
	content := readFile("/proc/1/cgroup")
	if content == "" {
		return false
	}
	for _, s := range []string{"docker", "lxc", "kubepods"} {
		if strings.Contains(content, s) {
			return true
		}
	}
	return false
}

func qemuFWCfg() bool {
	if _, err := os.Stat("/sys/firmware/qemu_fw_cfg"); err == nil {
		return true
	}
	if _, err := os.Stat("/proc/device-tree/fw-cfg"); err == nil {
		return true
	}
	return false
}

// --- Linux 65% ---

func dmiProduct() bool {
	name := readFile("/sys/class/dmi/id/product_name")
	switch {
	case strings.Contains(name, "VirtualBox"):
		return true
	case strings.Contains(name, "VMware"):
		return true
	case name == "KVM" || name == "QEMU":
		return true
	case strings.Contains(name, "Bochs"):
		return true
	case strings.Contains(name, "Hyper-V"):
		return true
	}
	return false
}

func dmiSysVendor() bool {
	vendor := readFile("/sys/class/dmi/id/sys_vendor")
	switch {
	case strings.Contains(vendor, "innotek"):
		return true
	case strings.Contains(vendor, "Xen"):
		return true
	case strings.Contains(vendor, "Microsoft"):
		return true
	case strings.Contains(vendor, "QEMU"):
		return true
	}
	return false
}

func dmiChassisVendor() bool {
	vendor := readFile("/sys/class/dmi/id/chassis_vendor")
	return strings.Contains(vendor, "Oracle") || strings.Contains(vendor, "innotek")
}

func vmwareIOMEM() bool {
	return strings.Contains(readFile("/proc/iomem"), "VMware")
}

func vmwareDMESG() bool {
	content := readFile("/var/log/dmesg")
	if content == "" {
		content = readFile("/var/log/kern.log")
	}
	return strings.Contains(content, "VMware") || strings.Contains(content, "vmware")
}

// --- Linux 55% ---

func dmidecodeCheck() bool {
	// Check all DMI sysfs files for VM brands
	entries, err := os.ReadDir("/sys/class/dmi/id")
	if err != nil {
		return false
	}
	patterns := []string{"virtualbox", "vmware", "qemu", "kvm", "innotek", "xen", "bochs"}
	for _, e := range entries {
		if e.IsDir() {
			continue
		}
		data, err := os.ReadFile(filepath.Join("/sys/class/dmi/id", e.Name()))
		if err != nil {
			continue
		}
		content := strings.ToLower(string(data))
		for _, p := range patterns {
			if strings.Contains(content, p) {
				return true
			}
		}
	}
	return false
}

func dmesgCheck() bool {
	content := readFile("/var/log/dmesg")
	if content == "" {
		return false
	}
	for _, s := range []string{"Hypervisor", "VMware", "VirtualBox", "QEMU", "KVM", "Xen"} {
		if strings.Contains(content, s) {
			return true
		}
	}
	return false
}

// --- Linux 50% ---

func dmiScan() bool {
	return firmwareDMI()
}

func smbiosVMBit() bool {
	// Check /sys/firmware/dmi/tables for SMBIOS entry point
	data, err := os.ReadFile("/sys/firmware/dmi/tables/smbios_entry_point")
	if err != nil {
		return false
	}
	// SMBIOS v3+ has a VM bit at offset 0x09
	if len(data) >= 0x1F {
		if data[0x1E]&0x01 != 0 {
			return true
		}
	}
	return false
}

func threadMismatchLinux() bool {
	cpuContent := readFile("/proc/cpuinfo")
	if cpuContent == "" {
		return false
	}
	n := 0
	for _, line := range strings.Split(cpuContent, "\n") {
		if strings.HasPrefix(line, "processor") {
			n++
		}
	}
	// If we see CPU brand but low core count, likely VM
	if n < 2 && strings.Contains(cpuContent, "Intel") || strings.Contains(cpuContent, "AMD") {
		return true
	}
	return false
}

func amdSEVMSR() bool {
	// Check /sys/kernel/security/sev for AMD SEV/SEV-ES/SEV-SNP
	if data, err := os.ReadFile("/sys/kernel/security/sev/version"); err == nil {
		return strings.TrimSpace(string(data)) != ""
	}
	// Also check MSR via /dev/cpu (requires root)
	data, err := os.ReadFile("/dev/cpu/0/msr")
	if err != nil {
		return false
	}
	if len(data) > 0x800 {
		_ = data[0x800]
	}
	return false
}

// --- Linux 40% ---

func vmwareSCSI() bool {
	return strings.Contains(readFile("/proc/scsi/scsi"), "VMware")
}

func qemuVirtualDMI() bool {
	content := readFile("/sys/devices/virtual/dmi/id/product_name")
	if strings.Contains(content, "QEMU") || strings.Contains(content, "KVM") || strings.Contains(content, "VirtualBox") {
		return true
	}
	vendor := readFile("/sys/devices/virtual/dmi/id/sys_vendor")
	return strings.Contains(vendor, "QEMU")
}

func processesLinux() bool {
	vmProcs := []string{"vbox", "vmware", "VBox", "VMware", "qemu", "QEMU", "xen", "Xen"}
	entries, err := os.ReadDir("/proc")
	if err != nil {
		return false
	}
	for _, e := range entries {
		if !e.IsDir() {
			continue
		}
		// Only check numeric directories (process PIDs)
		if e.Name()[0] < '0' || e.Name()[0] > '9' {
			continue
		}
		cmdline, err := os.ReadFile(filepath.Join("/proc", e.Name(), "comm"))
		if err != nil {
			continue
		}
		comm := strings.TrimSpace(string(cmdline))
		for _, p := range vmProcs {
			if strings.EqualFold(comm, p) {
				return true
			}
		}
	}
	return false
}

// --- Linux 35% ---

func systemdVirt() bool {
	// Check /sys/devices/virtual/dmi/id for VM indicators
	// In containers, /sys might not have DMI info
	vendor := readFile("/sys/devices/virtual/dmi/id/sys_vendor")
	if vendor == "" {
		// No DMI info — likely container
		return cgroupVM() || dockerEnv()
	}
	return strings.Contains(vendor, "QEMU") || strings.Contains(vendor, "Xen")
}

func hwmonMissing() bool {
	_, err := os.ReadDir("/sys/class/hwmon")
	return err != nil
}

// --- Linux 30% ---

func wslProc() bool {
	version := readFile("/proc/version")
	if strings.Contains(version, "Microsoft") || strings.Contains(version, "WSL") || strings.Contains(version, "microsoft") {
		return true
	}
	osrelease := readFile("/proc/sys/kernel/osrelease")
	return strings.Contains(osrelease, "Microsoft") || strings.Contains(osrelease, "WSL")
}

// --- Linux 20% ---

func hypervisorDir() bool {
	entries, err := os.ReadDir("/sys/hypervisor")
	if err != nil {
		return false
	}
	return len(entries) > 0
}

func temperatureCheck() bool {
	entries, err := os.ReadDir("/sys/class/thermal")
	if err != nil {
		return true // no thermal info = likely VM
	}
	return len(entries) < 2
}

func qemuUSB() bool {
	data, err := os.ReadFile("/sys/kernel/debug/usb/devices")
	if err != nil {
		return false
	}
	return strings.Contains(string(data), "QEMU")
}

func dmiChassisType() bool {
	typ := readFile("/sys/class/dmi/id/chassis_type")
	return typ == "" || typ == "0"
}

// --- Linux 15% ---

func vboxModule() bool {
	content := readFile("/proc/modules")
	return strings.Contains(content, "vbox")
}

func sysinfoProc() bool {
	content := readFile("/proc/sysinfo")
	return strings.Contains(content, "QEMU") || strings.Contains(content, "KVM") || strings.Contains(content, "Xen")
}

func fileAccessHistory() bool {
	// Check if there's very low file access history (common in fresh VMs)
	_, err := os.Stat("/root/.bash_history")
	hasHistory := err == nil
	_, err2 := os.Stat("/home")
	hasHome := err2 == nil
	return hasHome && !hasHistory
}

// --- Linux 10% ---

func linuxUserHost() bool {
	hostname := readFile("/proc/sys/kernel/hostname")
	if hostname == "" {
		return false
	}
	lower := strings.ToLower(hostname)
	for _, s := range []string{"ubuntu", "debian", "localhost"} {
		if lower == s || strings.HasPrefix(lower, s+"-") {
			return true
		}
	}
	return false
}

// --- Linux 5% ---

func podmanFile() bool {
	_, err := os.Stat("/run/.containerenv")
	return err == nil
}

func bluestacksFolders() bool {
	entries, err := os.ReadDir("/mnt")
	if err != nil {
		return false
	}
	for _, e := range entries {
		if strings.Contains(strings.ToLower(e.Name()), "bluestacks") {
			return true
		}
	}
	return false
}

func kmsgCheck() bool {
	f, err := os.OpenFile("/dev/kmsg", syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
	if err != nil {
		return false
	}
	defer f.Close()
	buf := make([]byte, 4096)
	n, err := f.Read(buf)
	if err != nil || n == 0 {
		return false
	}
	content := strings.ToLower(string(buf[:n]))
	return strings.Contains(content, "hypervisor") || strings.Contains(content, "kvm")
}
