package storage

import (
	"os"
	"path/filepath"
	"runtime"
	"testing"
)

// fakeDevDisk builds a temp /dev/disk tree: a real "device" file + by-id/by-uuid symlinks to it.
// Returns the disk root + the device path. (filepath.EvalSymlinks needs real targets, so the
// "device" is a regular file standing in for a block device.) Skips on Windows where creating a
// symlink needs a privilege — the durable-device code is Linux-only (the agent runs on the PVE
// host), so these run on the build server / demo host.
func fakeDevDisk(t *testing.T, links map[string]string, uuids map[string]string) (root, device string) {
	t.Helper()
	if runtime.GOOS == "windows" {
		t.Skip("durable-device symlink tests run on Linux (the agent's OS); symlink creation needs privilege on Windows")
	}
	base := t.TempDir()
	device = filepath.Join(base, "sdb")
	if err := os.WriteFile(device, []byte("x"), 0o600); err != nil {
		t.Fatal(err)
	}
	root = filepath.Join(base, "disk")
	mk := func(sub, name, target string) {
		dir := filepath.Join(root, sub)
		os.MkdirAll(dir, 0o755)
		if err := os.Symlink(target, filepath.Join(dir, name)); err != nil {
			t.Fatal(err)
		}
	}
	for name := range links {
		mk("by-id", name, device)
	}
	for uuid := range uuids {
		mk("by-uuid", uuid, device)
	}
	return root, device
}

func withDevDiskRoot(t *testing.T, root string) {
	t.Helper()
	old := devDiskRoot
	devDiskRoot = root
	t.Cleanup(func() { devDiskRoot = old })
}

// DeviceDurableID prefers a wwn- by-id link over ata-/serial links and over a uuid.
func TestDeviceDurableID_PrefersWWN(t *testing.T) {
	root, device := fakeDevDisk(t,
		map[string]string{"wwn-0x5000c500abcd": "", "ata-Samsung_SSD_850_S1": "", "scsi-35000c500abcd": ""},
		map[string]string{"1111-2222": ""})
	withDevDiskRoot(t, root)

	id, err := DeviceDurableID(device)
	if err != nil {
		t.Fatalf("DeviceDurableID: %v", err)
	}
	if id != "byid:wwn-0x5000c500abcd" {
		t.Errorf("durable id = %q, want the wwn link", id)
	}
}

// With no by-id link, it falls back to a filesystem UUID.
func TestDeviceDurableID_FallsBackToUUID(t *testing.T) {
	root, device := fakeDevDisk(t, map[string]string{}, map[string]string{"abcd-ef01": ""})
	withDevDiskRoot(t, root)
	id, err := DeviceDurableID(device)
	if err != nil {
		t.Fatalf("DeviceDurableID: %v", err)
	}
	if id != "byuuid:abcd-ef01" {
		t.Errorf("durable id = %q, want byuuid:abcd-ef01", id)
	}
}

// A device with no durable identity at all → error (cannot be wipe-bound).
func TestDeviceDurableID_NoneErrors(t *testing.T) {
	root, device := fakeDevDisk(t, map[string]string{}, map[string]string{})
	withDevDiskRoot(t, root)
	if _, err := DeviceDurableID(device); err == nil {
		t.Fatal("a device with no wwn/serial/uuid must error (no durable id)")
	}
}

// ResolveDurableDevice round-trips a by-id id back to the canonical device path.
func TestResolveDurableDevice_RoundTrip(t *testing.T) {
	root, device := fakeDevDisk(t, map[string]string{"wwn-0xabc": ""}, map[string]string{})
	withDevDiskRoot(t, root)
	got, err := ResolveDurableDevice("byid:wwn-0xabc")
	if err != nil {
		t.Fatalf("ResolveDurableDevice: %v", err)
	}
	want, _ := filepath.EvalSymlinks(device)
	if got != want {
		t.Errorf("resolved %q, want %q", got, want)
	}
}

// A path-only / unknown-scheme id is REFUSED (the anti-retarget invariant).
func TestResolveDurableDevice_RefusesPathOnly(t *testing.T) {
	withDevDiskRoot(t, t.TempDir())
	for _, bad := range []string{"/dev/sdb", "sdb", "uuid-no-scheme", "byid:../../etc/passwd", "byuuid:../x"} {
		if _, err := ResolveDurableDevice(bad); err == nil {
			t.Errorf("ResolveDurableDevice(%q) succeeded, want refusal", bad)
		}
	}
}

// A durable id whose link is gone → error (device removed/replaced).
func TestResolveDurableDevice_MissingErrors(t *testing.T) {
	withDevDiskRoot(t, t.TempDir()) // empty: no by-id dir
	if _, err := ResolveDurableDevice("byid:wwn-0xgone"); err == nil {
		t.Fatal("an absent durable id must error")
	}
}
