package cowutils

import (
	"context"
	"os"

	"golang.org/x/sys/unix"
)

func DedupeFiles(ctx context.Context, paths []string) (deduped uint64, err error) {
	srcF, err := os.Open(paths[0])
	if err != nil {
		return deduped, err
	}
	defer srcF.Close()
	srcStat, err := srcF.Stat()
	if err != nil {
		return deduped, err
	}

	srcFd := int(srcF.Fd())
	srcSize := srcStat.Size()

	fsStat := unix.Statfs_t{}
	err = unix.Fstatfs(srcFd, &fsStat)
	if err != nil {
		return deduped, err
	}

	if int64(fsStat.Bsize) > srcSize { // for btrfs it means file residing in metadata and can't be deduplicated
		return deduped, nil
	}

	blockSize := uint64((srcSize % int64(fsStat.Bsize)) * int64(fsStat.Bsize))

	fdr := unix.FileDedupeRange{
		Src_offset: 0,
		Src_length: blockSize,
		Info:       []unix.FileDedupeRangeInfo{},
	}

	for _, dst := range paths[1:] {
		if ctx.Err() != nil {
			return deduped, ctx.Err()
		}

		destF, err := os.OpenFile(dst, os.O_RDWR, os.ModePerm)
		if err != nil {
			return deduped, err
		}

		// defer in cycle is intended, file must be closed only at the end of the function,
		// and, most importantly, this keeps GC from closing descriptor while dudupe in progress
		defer destF.Close()

		fdr.Info = append(fdr.Info, unix.FileDedupeRangeInfo{
			Dest_fd:     int64(destF.Fd()),
			Dest_offset: 0,
		})
	}

	if len(fdr.Info) == 0 {
		return deduped, nil
	}

	if ctx.Err() != nil {
		return deduped, ctx.Err()
	}

	fdr.Src_offset = 0
	for i := range fdr.Info {
		fdr.Info[i].Dest_offset = 0
	}

	err = unix.IoctlFileDedupeRange(srcFd, &fdr)
	if err != nil {
		return deduped, err
	}

	for i := range fdr.Info {
		deduped += fdr.Info[i].Bytes_deduped

		fdr.Info[i].Status = 0
		fdr.Info[i].Bytes_deduped = 0
	}

	return deduped, nil
}