package model

import "fmt"

// VMDefaults captures the baseline sizing applied to a new VM when the
// user omits the corresponding --vcpu / --memory / --disk-size flags.
// Each field carries a Source tag explaining where the number came
// from so UI layers can surface provenance ("auto" vs "config" vs
// "built-in default").
type VMDefaults struct {
	VCPUCount             int
	MemoryMiB             int
	WorkDiskSizeBytes     int64
	SystemOverlaySizeByte int64

	// Source describes which layer won for each field, one of:
	// "config" — user set it in config.toml
	// "auto"   — computed from host resources
	// "builtin"— hardcoded fallback
	VCPUSource          string
	MemorySource        string
	WorkDiskSource      string
	SystemOverlaySource string
}

// VMDefaultsOverride is the optional block users can place in
// config.toml's [vm_defaults]. Zero-value fields mean "not set, let
// banger decide."
type VMDefaultsOverride struct {
	VCPUCount             int
	MemoryMiB             int
	WorkDiskSizeBytes     int64
	SystemOverlaySizeByte int64
}

// ResolveVMDefaults picks effective VM defaults from (in order) the
// user's config overrides, then host-derived heuristics, then baked-in
// constants. hostCPUs and hostMemoryBytes are what `system.ReadHost
// Resources` reports; 0 on either is treated as "unknown" and skipped,
// which pushes that field down to the builtin fallback.
func ResolveVMDefaults(override VMDefaultsOverride, hostCPUs int, hostMemoryBytes int64) VMDefaults {
	d := VMDefaults{
		VCPUCount:             DefaultVCPUCount,
		MemoryMiB:             DefaultMemoryMiB,
		WorkDiskSizeBytes:     DefaultWorkDiskSize,
		SystemOverlaySizeByte: DefaultSystemOverlaySize,
		VCPUSource:            "builtin",
		MemorySource:          "builtin",
		WorkDiskSource:        "builtin",
		SystemOverlaySource:   "builtin",
	}

	// vCPU: config > auto > builtin.
	switch {
	case override.VCPUCount > 0:
		d.VCPUCount = override.VCPUCount
		d.VCPUSource = "config"
	case hostCPUs > 0:
		d.VCPUCount = autoVCPU(hostCPUs)
		d.VCPUSource = "auto"
	}

	// Memory MiB: config > auto > builtin.
	switch {
	case override.MemoryMiB > 0:
		d.MemoryMiB = override.MemoryMiB
		d.MemorySource = "config"
	case hostMemoryBytes > 0:
		d.MemoryMiB = autoMemoryMiB(hostMemoryBytes)
		d.MemorySource = "auto"
	}

	// Work disk: config > builtin. Disk is a COW overlay — growing
	// the allocation with host RAM gives nothing useful, so no auto.
	if override.WorkDiskSizeBytes > 0 {
		d.WorkDiskSizeBytes = override.WorkDiskSizeBytes
		d.WorkDiskSource = "config"
	}

	// System overlay: config > builtin.
	if override.SystemOverlaySizeByte > 0 {
		d.SystemOverlaySizeByte = override.SystemOverlaySizeByte
		d.SystemOverlaySource = "config"
	}

	return d
}

// autoVCPU clamps cpus/4 into [1, 4]. A 2-vcpu sandbox is the sweet
// spot for most dev loops; going higher rarely helps interactive use
// and starves the host of cores.
func autoVCPU(hostCPUs int) int {
	candidate := hostCPUs / 4
	if candidate < 1 {
		candidate = 1
	}
	if candidate > 4 {
		candidate = 4
	}
	return candidate
}

// autoMemoryMiB caps at host/8, floor 1 GiB, ceiling 8 GiB. 1/8 leaves
// plenty of headroom for the host even if several VMs run
// concurrently; 8 GiB is enough for most language toolchains without
// being hostile on 32 GiB laptops.
func autoMemoryMiB(hostMemoryBytes int64) int {
	const (
		mib       = int64(1024 * 1024)
		gib       = 1024 * mib
		floorMiB  = 1024     // 1 GiB
		cappedMiB = 8 * 1024 // 8 GiB
	)
	candidate := hostMemoryBytes / 8 / mib
	if candidate < floorMiB {
		candidate = floorMiB
	}
	if candidate > cappedMiB {
		candidate = cappedMiB
	}
	// Round down to 256 MiB multiples for tidier output.
	candidate -= candidate % 256
	if candidate < floorMiB {
		candidate = floorMiB
	}
	return int(candidate)
}

// FormatSpecLine renders a one-line summary of VM sizing suitable for
// progress output or doctor display.
func (d VMDefaults) FormatSpecLine() string {
	return fmt.Sprintf("%d vcpu · %d MiB · %s disk",
		d.VCPUCount, d.MemoryMiB, FormatSizeBytes(d.WorkDiskSizeBytes))
}
