package util

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
)

// EnsureDir 创建目录（包含父目录），若已存在则忽略。
func EnsureDir(path string, perm os.FileMode) error {
	if path == "" {
		return fmt.Errorf("目录路径为空")
	}
	return os.MkdirAll(path, perm)
}

// EnsureParentDir 确保文件父目录存在。
func EnsureParentDir(path string, perm os.FileMode) error {
	dir := filepath.Dir(path)
	return EnsureDir(dir, perm)
}

// AtomicWriteFile 采用临时文件写入后原子替换。
func AtomicWriteFile(path string, data []byte, perm os.FileMode) error {
	if err := EnsureParentDir(path, 0o755); err != nil {
		return err
	}

	dir := filepath.Dir(path)
	tmpFile, err := os.CreateTemp(dir, "tmp-*")
	if err != nil {
		return err
	}
	tmpName := tmpFile.Name()
	defer func() {
		tmpFile.Close()
		os.Remove(tmpName) // 忽略错误
	}()

	if _, err := tmpFile.Write(data); err != nil {
		return err
	}
	if err := tmpFile.Sync(); err != nil {
		return err
	}
	if err := tmpFile.Close(); err != nil {
		return err
	}
	if err := os.Chmod(tmpName, perm); err != nil {
		return err
	}
	return os.Rename(tmpName, path)
}

// CopyFile 复制文件内容（覆盖目标文件），并设置权限。
func CopyFile(src, dst string, perm os.FileMode) error {
	if err := EnsureParentDir(dst, 0o755); err != nil {
		return err
	}
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()

	out, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
	if err != nil {
		return err
	}
	defer out.Close()

	if _, err := io.Copy(out, in); err != nil {
		return err
	}
	return out.Sync()
}

// BackupFile 如果目标文件存在则创建 .bak 备份。
func BackupFile(path string) (string, error) {
	if _, err := os.Stat(path); err != nil {
		if os.IsNotExist(err) {
			return "", nil
		}
		return "", err
	}
	backupPath := path + ".bak"
	if err := CopyFile(path, backupPath, 0o644); err != nil {
		return "", err
	}
	return backupPath, nil
}

// EnsureExecutable 确保文件具有可执行权限（Windows 下忽略）。
func EnsureExecutable(path string) error {
	info, err := os.Stat(path)
	if err != nil {
		return err
	}
	mode := info.Mode()
	if mode&0o111 != 0 {
		return nil
	}
	return os.Chmod(path, mode|0o111)
}
