package api

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

	"clicd/internal/config"
)

func TestIsUsableStorageMount(t *testing.T) {
	tests := []struct {
		name       string
		deviceType string
		fsType     string
		devicePath string
		mountPoint string
		readOnly   bool
		wantUsable bool
	}{
		{name: "root partition", deviceType: "part", fsType: "ext4", devicePath: "/dev/sda2", mountPoint: "/", wantUsable: true},
		{name: "mounted data disk", deviceType: "disk", fsType: "xfs", devicePath: "/dev/sdb", mountPoint: "/data", wantUsable: true},
		{name: "snap loop", deviceType: "loop", fsType: "squashfs", devicePath: "/dev/loop0", mountPoint: "/snap/core20/2105", readOnly: true},
		{name: "loop without ro flag", deviceType: "loop", fsType: "ext4", devicePath: "/dev/loop7", mountPoint: "/mnt/loop"},
		{name: "read only disk", deviceType: "part", fsType: "ext4", devicePath: "/dev/sdc1", mountPoint: "/archive", readOnly: true},
		{name: "optical image", deviceType: "rom", fsType: "iso9660", devicePath: "/dev/sr0", mountPoint: "/media/cdrom"},
		{name: "efi partition", deviceType: "part", fsType: "vfat", devicePath: "/dev/sda1", mountPoint: "/boot/efi"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := isUsableStorageMount(tt.deviceType, tt.fsType, tt.devicePath, tt.mountPoint, tt.readOnly)
			if got != tt.wantUsable {
				t.Fatalf("isUsableStorageMount() = %v, want %v", got, tt.wantUsable)
			}
		})
	}
}

func TestBestMountPointForPath(t *testing.T) {
	disks := []storageDiskInfo{
		{Path: "/dev/sda2", MountPoint: "/"},
		{Path: "/dev/sdb1", MountPoint: "/mnt/clicd-data"},
	}
	tests := []struct {
		path string
		want string
	}{
		{path: "/var/lib/clicd", want: "/"},
		{path: "/mnt/clicd-data/clicd", want: "/mnt/clicd-data"},
		{path: "/mnt/clicd-data", want: "/mnt/clicd-data"},
	}
	for _, tt := range tests {
		if got := bestMountPointForPath(tt.path, disks); got != tt.want {
			t.Fatalf("bestMountPointForPath(%q) = %q, want %q", tt.path, got, tt.want)
		}
	}
}

func TestNormalizeStoragePoolsUsesServerManagedPath(t *testing.T) {
	disks := []storageDiskInfo{
		{Path: "/dev/sda2", MountPoint: "/"},
		{Path: "/dev/sdb1", MountPoint: "/mnt/data"},
	}
	items := []config.StoragePool{{
		ID:              "disk-data",
		Name:            "data",
		Path:            "/mnt/data/clicd",
		MountPoint:      "/mnt/data",
		ContentTypes:    []string{config.StorageContentLXC},
		DefaultContents: []string{config.StorageContentLXC},
		Enabled:         true,
	}}
	pools, err := normalizeStoragePoolsRequestWithDisks(items, disks)
	if err != nil {
		t.Fatal(err)
	}
	wantPath := filepath.Join(filepath.Clean("/mnt/data"), "clicd")
	if len(pools) != 1 || pools[0].ID != "disk-data" || pools[0].Name != "data (/dev/sdb1)" || pools[0].Path != wantPath || pools[0].MountPoint != "/mnt/data" {
		t.Fatalf("unexpected normalized pools: %#v", pools)
	}
}

func TestNormalizeStoragePoolsRejectsUncontrolledPath(t *testing.T) {
	disks := []storageDiskInfo{{Path: "/dev/sdb1", MountPoint: "/mnt/data"}}
	for _, path := range []string{"/etc", "/mnt/data/clicd/../../etc", "/mnt/data/other"} {
		_, err := normalizeStoragePoolsRequestWithDisks([]config.StoragePool{{
			ID:         "disk-data",
			Name:       "data",
			Path:       path,
			MountPoint: "/mnt/data",
			Enabled:    true,
		}}, disks)
		if err == nil {
			t.Fatalf("path %q was accepted", path)
		}
	}
}

func TestDirSizeBytesUsesAllocatedBlocks(t *testing.T) {
	if runtime.GOOS != "linux" {
		t.Skip("allocated-block behavior is provided by the Linux du command")
	}
	dir := t.TempDir()
	file, err := os.Create(filepath.Join(dir, "sparse.img"))
	if err != nil {
		t.Fatal(err)
	}
	if err := file.Truncate(1 << 30); err != nil {
		file.Close()
		t.Fatal(err)
	}
	if err := file.Close(); err != nil {
		t.Fatal(err)
	}

	if got := dirSizeBytes(dir); got >= 128<<20 {
		t.Fatalf("dirSizeBytes() = %d, expected allocated size instead of 1 GiB apparent size", got)
	}
}
