package daemon

import (
	"context"
	"path/filepath"
	"strings"
	"testing"

	"banger/internal/model"
	"banger/internal/paths"
)

// TestReserveVMAllowsNameThatPrefixesExistingVM is a regression for a
// correctness bug in the name-uniqueness check: reserveVM used to
// route through FindVM, which falls back to prefix-matching on both
// ids and names. That meant a perfectly valid new name like "beta"
// could be rejected simply because an existing VM's id or name
// started with "beta". Exact-name lookup via store.GetVMByName fixes
// it. The test seeds a VM whose id and name are long strings, then
// tries to reserve a new VM with a name that's a prefix of each —
// both must succeed.
func TestReserveVMAllowsNameThatPrefixesExistingVM(t *testing.T) {
	ctx := context.Background()
	tmp := t.TempDir()
	d := &Daemon{
		store:  openDaemonStore(t),
		layout: paths.Layout{VMsDir: filepath.Join(tmp, "vms"), RuntimeDir: filepath.Join(tmp, "runtime")},
		config: model.DaemonConfig{BridgeIP: model.DefaultBridgeIP},
	}
	wireServices(d)

	existing := testVM("longname-sandbox-foobar", "image-x", "172.16.0.50")
	upsertDaemonVM(t, ctx, d.store, existing)

	image := testImage("image-x")
	image.ID = "image-x"
	image.Name = "image-x"
	if err := d.store.UpsertImage(ctx, image); err != nil {
		t.Fatalf("UpsertImage: %v", err)
	}

	// New VM name is a prefix of the existing id (which is
	// "longname-sandbox-foobar-id" per testVM). Old FindVM-based check
	// would reject this.
	if vm, err := d.vm.reserveVM(ctx, "longname", image, model.VMSpec{VCPUCount: 1, MemoryMiB: 128}); err != nil {
		t.Fatalf("reserveVM(prefix of id): %v", err)
	} else if vm.Name != "longname" {
		t.Fatalf("reserveVM returned name=%q, want longname", vm.Name)
	}

	// Prefix of the existing name ("longname-sandbox") must also work.
	if vm, err := d.vm.reserveVM(ctx, "longname-sandbox", image, model.VMSpec{VCPUCount: 1, MemoryMiB: 128}); err != nil {
		t.Fatalf("reserveVM(prefix of name): %v", err)
	} else if vm.Name != "longname-sandbox" {
		t.Fatalf("reserveVM returned name=%q, want longname-sandbox", vm.Name)
	}
}

// TestReserveVMRejectsExactDuplicateName confirms the uniqueness
// check still catches actual collisions after the FindVM → GetVMByName
// switch.
func TestReserveVMRejectsExactDuplicateName(t *testing.T) {
	ctx := context.Background()
	tmp := t.TempDir()
	d := &Daemon{
		store:  openDaemonStore(t),
		layout: paths.Layout{VMsDir: filepath.Join(tmp, "vms"), RuntimeDir: filepath.Join(tmp, "runtime")},
		config: model.DaemonConfig{BridgeIP: model.DefaultBridgeIP},
	}
	wireServices(d)
	existing := testVM("sandbox", "image-x", "172.16.0.51")
	upsertDaemonVM(t, ctx, d.store, existing)

	image := testImage("image-x")
	image.ID = "image-x"
	image.Name = "image-x"
	if err := d.store.UpsertImage(ctx, image); err != nil {
		t.Fatalf("UpsertImage: %v", err)
	}

	_, err := d.vm.reserveVM(ctx, "sandbox", image, model.VMSpec{VCPUCount: 1, MemoryMiB: 128})
	if err == nil {
		t.Fatal("reserveVM with duplicate name should have failed")
	}
	if !strings.Contains(err.Error(), "already exists") {
		t.Fatalf("err = %v, want 'already exists'", err)
	}
}

// TestReserveVMRejectsInvalidName pins defense-in-depth: the CLI
// already validates, but any other RPC caller (banger SDK, direct
// JSON over the socket) lands here without going through the CLI.
// The name ends up in /etc/hostname, kernel boot args, DNS records,
// and file paths — the daemon must refuse anything that isn't a
// valid DNS label.
func TestReserveVMRejectsInvalidName(t *testing.T) {
	ctx := context.Background()
	tmp := t.TempDir()
	d := &Daemon{
		store:  openDaemonStore(t),
		layout: paths.Layout{VMsDir: filepath.Join(tmp, "vms"), RuntimeDir: filepath.Join(tmp, "runtime")},
		config: model.DaemonConfig{BridgeIP: model.DefaultBridgeIP},
	}
	wireServices(d)

	image := testImage("image-x")
	image.ID = "image-x"
	image.Name = "image-x"
	if err := d.store.UpsertImage(ctx, image); err != nil {
		t.Fatalf("UpsertImage: %v", err)
	}

	for _, bad := range []string{
		"MyBox",       // uppercase
		"my box",      // space
		"my.box",      // dot
		"box\n",       // newline
		"-box",        // leading hyphen
		"box/../evil", // path separator + traversal
	} {
		if _, err := d.vm.reserveVM(ctx, bad, image, model.VMSpec{VCPUCount: 1, MemoryMiB: 128}); err == nil {
			t.Fatalf("reserveVM(%q) = nil error, want rejection", bad)
		}
	}
}
