package storage

import (
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

func TestParseFstab(t *testing.T) {
	data := `# /etc/fstab
UUID=1111-2222 /        ext4  defaults        0 1

/dev/sdb1      /mnt/data ext4 rw,noatime      0 2
LABEL=swap     none     swap  sw
`
	got := parseFstab(data)
	want := []FstabEntry{
		{Device: "UUID=1111-2222", Mountpoint: "/", FSType: "ext4", Options: "defaults", Dump: 0, Pass: 1},
		{Device: "/dev/sdb1", Mountpoint: "/mnt/data", FSType: "ext4", Options: "rw,noatime", Dump: 0, Pass: 2},
		{Device: "LABEL=swap", Mountpoint: "none", FSType: "swap", Options: "sw", Dump: 0, Pass: 0},
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("parseFstab:\n got %+v\nwant %+v", got, want)
	}
}

func TestFstabSurgicalEdits(t *testing.T) {
	path := filepath.Join(t.TempDir(), "fstab")
	old := fstabFile
	fstabFile = path
	defer func() { fstabFile = old }()

	// Seed with a comment + an existing entry that must survive edits.
	seed := "# keep me\nUUID=root / ext4 defaults 0 1\n"
	if err := os.WriteFile(path, []byte(seed), 0644); err != nil {
		t.Fatal(err)
	}

	// Append a new entry.
	if err := appendFstabLine(FstabEntry{Device: "/dev/sdb1", Mountpoint: "/mnt/data", FSType: "xfs", Options: "noatime", Pass: 2}); err != nil {
		t.Fatal(err)
	}
	entries, err := readFstab()
	if err != nil {
		t.Fatal(err)
	}
	if len(entries) != 2 || findEntry(entries, "/mnt/data") == nil {
		t.Fatalf("append failed: %+v", entries)
	}

	// Remove it again; the comment and original entry must remain.
	removed, err := removeFstabLines("/mnt/data")
	if err != nil || !removed {
		t.Fatalf("remove failed: removed=%v err=%v", removed, err)
	}
	data, _ := os.ReadFile(path)
	if !strings.Contains(string(data), "# keep me") || !strings.Contains(string(data), "UUID=root") {
		t.Errorf("surgical edit clobbered other lines:\n%s", data)
	}
	if findEntry(mustReadFstab(t), "/mnt/data") != nil {
		t.Error("entry still present after remove")
	}

	// Removing a missing mountpoint reports not-removed.
	if removed, _ := removeFstabLines("/nope"); removed {
		t.Error("expected removed=false for missing mountpoint")
	}
}

func mustReadFstab(t *testing.T) []FstabEntry {
	t.Helper()
	e, err := readFstab()
	if err != nil {
		t.Fatal(err)
	}
	return e
}

func TestValidateEntry(t *testing.T) {
	for _, tt := range []struct {
		name  string
		entry FstabEntry
		valid bool
	}{
		{name: "valid", entry: FstabEntry{Device: "/dev/sdb1", Mountpoint: "/mnt/data", FSType: "ext4", Options: "defaults"}, valid: true},
		{name: "UUID", entry: FstabEntry{Device: "UUID=ab-cd", Mountpoint: "/mnt/x", FSType: "xfs", Options: "rw,noatime"}, valid: true},
		{name: "shell metachar in device", entry: FstabEntry{Device: "/dev/sdb1; rm -rf /", Mountpoint: "/mnt/x", FSType: "ext4", Options: "defaults"}, valid: false},
		{name: "relative mountpoint", entry: FstabEntry{Device: "/dev/sdb1", Mountpoint: "../etc", FSType: "ext4", Options: "defaults"}, valid: false},
		{name: "traversal in mountpoint", entry: FstabEntry{Device: "/dev/sdb1", Mountpoint: "/mnt/../../etc", FSType: "ext4", Options: "defaults"}, valid: false},
		{name: "bad fstype", entry: FstabEntry{Device: "/dev/sdb1", Mountpoint: "/mnt/x", FSType: "ext4!", Options: "defaults"}, valid: false},
		{name: "bad options", entry: FstabEntry{Device: "/dev/sdb1", Mountpoint: "/mnt/x", FSType: "ext4", Options: "defaults; reboot"}, valid: false},
	} {
		t.Run(tt.name, func(t *testing.T) {
			err := validateEntry(tt.entry)
			if tt.valid && err != nil {
				t.Errorf("valid entry rejected: %v", err)
			}
			if !tt.valid && err == nil {
				t.Errorf("bad entry accepted: %+v", tt.entry)
			}
		})
	}
}
