package fileutil

import (
	"os"
	"path/filepath"
)

// WriteFileAtomically writes to a file path atomically. It does this by creating a temporary
// file in the same directory and then renaming it. If anything goes wrong the temporary
// file is deleted.
func WriteFileAtomically(filePath string, data []byte, mode os.FileMode) error {
	f, err := os.CreateTemp(filepath.Dir(filePath), filepath.Base(filePath)+".tmp")
	if err != nil {
		return err
	}
	tmpPath := f.Name()

	err = writeFileAndClose(f, data, mode)
	if err != nil {
		_ = os.Remove(tmpPath)
		return err
	}

	err = os.Rename(tmpPath, filePath)
	if err != nil {
		_ = os.Remove(tmpPath)
		return err
	}

	return nil
}

func writeFileAndClose(f *os.File, data []byte, mode os.FileMode) error {
	_, err := f.Write(data)
	if err != nil {
		_ = f.Close()
		return err
	}

	err = f.Sync()
	if err != nil {
		_ = f.Close()
		return err
	}

	err = f.Chmod(mode)
	if err != nil {
		_ = f.Close()
		return err
	}

	err = f.Close()
	if err != nil {
		return err
	}

	return nil
}