package zfs_test

import (
	"fmt"
	"testing"

	zfs "github.com/bicomsystems/go-libzfs"
)

/* ------------------------------------------------------------------------- */
// HELPERS:
var TSTDatasetPath = TSTPoolName + "/DATASET"
var TSTVolumePath = TSTDatasetPath + "/VOLUME"
var TSTDatasetPathSnap = TSTDatasetPath + "@test"

func printDatasets(ds []zfs.Dataset) error {
	for _, d := range ds {

		path, err := d.Path()
		if err != nil {
			return err
		}
		p, err := d.GetProperty(zfs.DatasetPropType)
		if err != nil {
			return err
		}
		fmt.Printf(" %30s | %10s\n", path, p.Value)
		if len(d.Children) > 0 {
			printDatasets(d.Children)
		}
	}
	return nil
}

/* ------------------------------------------------------------------------- */
// TESTS:

func zfsTestDatasetCreate(t *testing.T) {
	// reinit names used in case TESTPOOL was in conflict
	TSTDatasetPath = TSTPoolName + "/DATASET"
	TSTVolumePath = TSTDatasetPath + "/VOLUME"
	TSTDatasetPathSnap = TSTDatasetPath + "@test"

	println("TEST DatasetCreate(", TSTDatasetPath, ") (filesystem) ... ")
	props := make(map[zfs.Prop]zfs.Property)
	d, err := zfs.DatasetCreate(TSTDatasetPath, zfs.DatasetTypeFilesystem, props)
	if err != nil {
		t.Error(err)
		return
	}
	d.Close()
	print("PASS\n\n")

	strSize := "536870912" // 512M

	println("TEST DatasetCreate(", TSTVolumePath, ") (volume) ... ")
	props[zfs.DatasetPropVolsize] = zfs.Property{Value: strSize}
	// In addition I explicitly choose some more properties to be set.
	props[zfs.DatasetPropVolblocksize] = zfs.Property{Value: "4096"}
	props[zfs.DatasetPropReservation] = zfs.Property{Value: strSize}
	d, err = zfs.DatasetCreate(TSTVolumePath, zfs.DatasetTypeVolume, props)
	if err != nil {
		t.Error(err)
		return
	}
	d.Close()
	print("PASS\n\n")
}

func zfsTestDatasetOpen(t *testing.T) {
	println("TEST DatasetOpen(", TSTDatasetPath, ") ... ")
	d, err := zfs.DatasetOpen(TSTDatasetPath)
	if err != nil {
		t.Error(err)
		return
	}
	defer d.Close()
	print("PASS\n\n")

	println("TEST Set/GetUserProperty(prop, value string) ... ")
	var p zfs.Property
	// Test set/get user property
	if err = d.SetUserProperty("go-libzfs:test", "yes"); err != nil {
		t.Error(err)
		return
	}
	if p, err = d.GetUserProperty("go-libzfs:test"); err != nil {
		t.Error(err)
		return
	}
	println("go-libzfs:test", " = ",
		p.Value)
	print("PASS\n\n")
}

func zfsTestDatasetSetProperty(t *testing.T) {
	println("TEST Dataset SetProp(", TSTDatasetPath, ") ... ")
	d, err := zfs.DatasetOpen(TSTDatasetPath)
	if err != nil {
		t.Error(err)
		return
	}
	defer d.Close()
	if err = d.SetProperty(zfs.DatasetPropOverlay, "on"); err != nil {
		t.Error(err)
		return
	}
	if prop, err := d.GetProperty(zfs.DatasetPropOverlay); err != nil {
		t.Error(err)
		return
	} else {
		println(prop.Value)
		if prop.Value != "on" {
			t.Error(fmt.Errorf("Update of dataset property failed"))
			return
		}
	}
	print("PASS\n\n")
	return
}

func zfsTestDatasetOpenAll(t *testing.T) {
	println("TEST DatasetOpenAll()/DatasetCloseAll() ... ")
	ds, err := zfs.DatasetOpenAll()
	if err != nil {
		t.Error(err)
		return
	}
	if err = printDatasets(ds); err != nil {
		zfs.DatasetCloseAll(ds)
		t.Error(err)
		return
	}
	zfs.DatasetCloseAll(ds)
	print("PASS\n\n")
}

func zfsTestDatasetSnapshot(t *testing.T) {
	println("TEST DatasetSnapshot(", TSTDatasetPath, ", true, ...) ... ")
	props := make(map[zfs.Prop]zfs.Property)
	d, err := zfs.DatasetSnapshot(TSTDatasetPathSnap, true, props)
	if err != nil {
		t.Error(err)
		return
	}
	defer d.Close()
	print("PASS\n\n")
}

func zfsTestDatasetHoldRelease(t *testing.T) {
	println("TEST Hold/Release(", TSTDatasetPathSnap, ", true, ...) ... ")
	d, err := zfs.DatasetOpen(TSTDatasetPathSnap)
	if err != nil {
		t.Error(err)
		return
	}
	defer d.Close()
	err = d.Hold("keep")
	if err != nil {
		t.Error(err)
		return
	}

	var tags []zfs.HoldTag
	tags, err = d.Holds()
	if err != nil {
		t.Error(err)
		return
	}
	for _, tag := range tags {
		println("tag:", tag.Name, "timestamp:", tag.Timestamp.String())
	}

	err = d.Release("keep")
	if err != nil {
		t.Error(err)
		return
	}

	tags, err = d.Holds()
	if err != nil {
		t.Error(err)
		return
	}
	for _, tag := range tags {
		println("* tag:", tag.Name, "timestamp:", tag.Timestamp.String())
	}
	print("PASS\n\n")
}

func zfsTestSendSize(t *testing.T) {
	var size int64
	println("TEST SendSize(", TSTDatasetPathSnap, ") ... ")
	d, err := zfs.DatasetOpen(TSTDatasetPathSnap)
	if err != nil {
		t.Error(err)
		return
	}
	defer d.Close()
	if size, err = d.SendSize("", zfs.SendFlags{Compress: true}); err != nil {
		t.Error(err)
		return
	}
	if size <= 0 {
		t.Error(fmt.Errorf("Failed to fetch size. size = %d", size))
		return
	}
	print("PASS\n\n")
}

func zfsTestResumeTokenUnpack(t *testing.T) {
	var resToken zfs.ResumeToken
	println("TEST ResumeTokenUnpack ... ")
	err := resToken.Unpack("1-2111998041-170-789c636064000310a501c49c50360710a715e5e7a69766a6304001bfd66a579708b10d0a40363b92bafca4acd4e412081f0430e4d3d28a5381f20c0f94f218a1f26c48f2499525a9c540fac6d3fd6cd8f497e4435cb1f891ebba68d955ce3390e439c1f27989b9a90c0c7eae21c121fe41fa29f9b9899979bae646a98966c9166686c9496926262969894606a64966e629a686466946268689fa65f939bac9c996e68646a906e606962926a949e696c989a6a989468926a99666264646e60ec995c939a9ba86c6c98986494666692649294926e6a916c9498929c646c64916408d40d3e1fee666408467727e6e41516a71717e36031c0000cb4c43f4")
	if err != nil {
		t.Error(err)
		return
	}
	println("ResumeToken:", fmt.Sprintf("%v", resToken))
	return
}

func zfsTestDatasetDestroy(t *testing.T) {
	println("TEST DATASET Destroy( ", TSTDatasetPath, " ) ... ")
	d, err := zfs.DatasetOpen(TSTDatasetPath)
	if err != nil {
		t.Error(err)
		return
	}
	defer d.Close()
	if err = d.DestroyRecursive(); err != nil {
		t.Error(err)
		return
	}
	print("PASS\n\n")
}

func zfsTestMountPointConcurrency(t *testing.T) {
	println("TEST DATASET MountPointConcurrency( ", TSTDatasetPath, " ) ... ")
	d, err := zfs.DatasetOpen(TSTDatasetPath)
	if err != nil {
		t.Error(err)
		return
	}
	defer d.Close()
	gr1 := make(chan bool)
	gr2 := make(chan bool)
	go func() {
		for i := 0; i < 100; i++ {
			println("reload properties:", i)
			// d.SetProperty(zfs.DatasetPropMountpoint, "/TEST")
			d.ReloadProperties()
		}
		gr1 <- true
	}()
	go func() {
		for i := 0; i < 100; i++ {
			println("set mountpoint:", i)
			d.ReloadProperties()
			// d.SetProperty(zfs.DatasetPropMountpoint, "/TEST")
			// d.GetProperty(zfs.DatasetPropMountpoint)
		}
		gr2 <- true
	}()

	d.SetProperty(zfs.DatasetPropMountpoint, "none")

	<-gr1
	<-gr2
}

/* ------------------------------------------------------------------------- */
// EXAMPLES:

// Example of creating ZFS volume
func ExampleDatasetCreate() {
	// Create map to represent ZFS dataset properties. This is equivalent to
	// list of properties you can get from ZFS CLI tool, and some more
	// internally used by libzfs.
	props := make(map[zfs.Prop]zfs.Property)

	// I choose to create (block) volume 1GiB in size. Size is just ZFS dataset
	// property and this is done as map of strings. So, You have to either
	// specify size as base 10 number in string, or use strconv package or
	// similar to convert in to string (base 10) from numeric type.
	strSize := "1073741824"

	props[zfs.DatasetPropVolsize] = zfs.Property{Value: strSize}
	// In addition I explicitly choose some more properties to be set.
	props[zfs.DatasetPropVolblocksize] = zfs.Property{Value: "4096"}
	props[zfs.DatasetPropReservation] = zfs.Property{Value: strSize}

	// Lets create desired volume
	d, err := zfs.DatasetCreate("TESTPOOL/VOLUME1", zfs.DatasetTypeVolume, props)
	if err != nil {
		println(err.Error())
		return
	}
	// Dataset have to be closed for memory cleanup
	defer d.Close()

	println("Created zfs volume TESTPOOL/VOLUME1")
}

func ExampleDatasetOpen() {
	// Open dataset and read its available space
	d, err := zfs.DatasetOpen("TESTPOOL/DATASET1")
	if err != nil {
		panic(err.Error())
	}
	defer d.Close()
	var p zfs.Property
	if p, err = d.GetProperty(zfs.DatasetPropAvailable); err != nil {
		panic(err.Error())
	}
	println(zfs.DatasetPropertyToName(zfs.DatasetPropAvailable), " = ",
		p.Value)
}

func ExampleDatasetOpenAll() {
	datasets, err := zfs.DatasetOpenAll()
	if err != nil {
		panic(err.Error())
	}
	defer zfs.DatasetCloseAll(datasets)

	// Print out path and type of root datasets
	for _, d := range datasets {
		path, err := d.Path()
		if err != nil {
			panic(err.Error())
		}
		p, err := d.GetProperty(zfs.DatasetPropType)
		if err != nil {
			panic(err.Error())
		}
		fmt.Printf("%30s | %10s\n", path, p.Value)
	}

}

func CopyAndDestroy(d *zfs.Dataset) (err error) {
	if err = d.Destroy(false); err != nil {
		return
	}
	d.Close()
	return
}

func zfsTestDoubleFreeOnDestroy(t *testing.T) {
	TSTDestroyPath := TSTPoolName + "/DESTROY"
	println("TEST Doble Free On Destroy( ", TSTVolumePath, " ) ... ")
	props := make(map[zfs.Prop]zfs.Property)
	d, err := zfs.DatasetCreate(TSTDestroyPath, zfs.DatasetTypeFilesystem, props)
	if err != nil {
		t.Error(err)
		return
	}
	d.Close()

	d, err = zfs.DatasetOpen(TSTDestroyPath)
	if err != nil {
		t.Error(err)
		return
	}
	defer d.Close()
	if err = CopyAndDestroy(&d); err != nil {
		t.Error(err)
		return
	}
	print("PASS\n\n")
}
