package sparsefile

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

	"github.com/stretchr/testify/require"

	"github.com/kopia/kopia/internal/stat"
)

func TestSparseCopy(t *testing.T) {
	t.Parallel()

	if runtime.GOOS == "windows" {
		t.Skip("sparse files are not supported on windows")
	}

	dir := t.TempDir()

	blk, err := stat.GetBlockSize(dir)
	require.NoError(t, err)

	type chunk struct {
		slice []byte
		off   uint64
		rep   uint64
	}

	cases := []struct {
		name string
		size uint64
		data []chunk
	}{
		{
			name: "null",
			size: 0,
		},
		{
			name: "empty",
			size: blk,
			data: []chunk{
				{slice: []byte{0}, off: 0, rep: blk},
			},
		},
		{
			name: "hole",
			size: 2 * blk,
			data: []chunk{
				{slice: []byte{1}, off: blk, rep: blk},
			},
		},
		{
			name: "mix",
			size: 2 * blk,
			data: []chunk{
				{slice: []byte{1}, off: 3, rep: blk - 10},
				{slice: []byte{1}, off: 2*blk - 10, rep: 10},
			},
		},
	}

	for _, c := range cases {
		src := filepath.Join(dir, "src"+c.name)
		dst := filepath.Join(dir, "dst"+c.name)

		sf, err := os.Create(src)
		if err != nil {
			t.Fatal(err)
		}

		for _, d := range c.data {
			sf.WriteAt(bytes.Repeat(d.slice, int(d.rep)), int64(d.off))
		}

		df, err := os.Create(dst)
		if err != nil {
			t.Fatal(err)
		}

		if err = df.Truncate(int64(c.size)); err != nil {
			t.Fatal(err)
		}

		blk, err := stat.GetBlockSize(dst)
		if err != nil {
			t.Fatal(err)
		}

		_, err = Copy(df, sf, blk)
		if err != nil {
			t.Fatalf("error writing %s: %v", dst, err)
		}

		s, err := os.ReadFile(src)
		if err != nil {
			t.Fatal(err)
		}

		d, err := os.ReadFile(dst)
		if err != nil {
			t.Fatal(err)
		}

		if !bytes.Equal(s, d) {
			t.Fatalf("contents of %s and %s are not identical", src, dst)
		}
	}
}
