// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

//go:build windows

package replacefile

import (
	"os"
	"syscall"

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

// AtomicRename renames from the source path to the destination path,
// atomically replacing any file that might already exist at the destination.
//
// Typically this operation can succeed only if the source and destination
// are within the same physical filesystem, so this function is best reserved
// for cases where the source and destination exist in the same directory and
// only the local filename differs between them.
func AtomicRename(source, destination string) error {
	// On Windows, renaming one file over another is not atomic and certain
	// error conditions can result in having only the source file and nothing
	// at the destination file. Instead, we need to call into the MoveFileEx
	// Windows API function, setting two flags to opt in to replacing an
	// existing file.
	srcPtr, err := syscall.UTF16PtrFromString(source)
	if err != nil {
		return &os.LinkError{Op: "replace", Old: source, New: destination, Err: err}
	}
	destPtr, err := syscall.UTF16PtrFromString(destination)
	if err != nil {
		return &os.LinkError{Op: "replace", Old: source, New: destination, Err: err}
	}

	flags := uint32(windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH)
	err = windows.MoveFileEx(srcPtr, destPtr, flags)
	if err != nil {
		return &os.LinkError{Op: "replace", Old: source, New: destination, Err: err}
	}
	return nil
}
