package agent

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"

	"github.com/henrygd/beszel/agent/utils"
)

// GetDataDir returns the path to the data directory for the agent and an error
// if the directory is not valid. Attempts to find the optimal data directory if
// no data directories are provided.
func GetDataDir(dataDirs ...string) (string, error) {
	if len(dataDirs) > 0 {
		return testDataDirs(dataDirs)
	}

	dataDir, _ := utils.GetEnv("DATA_DIR")
	if dataDir != "" {
		dataDirs = append(dataDirs, dataDir)
	}

	if runtime.GOOS == "windows" {
		dataDirs = append(dataDirs,
			filepath.Join(os.Getenv("APPDATA"), "beszel-agent"),
			filepath.Join(os.Getenv("LOCALAPPDATA"), "beszel-agent"),
		)
	} else {
		dataDirs = append(dataDirs, "/var/lib/beszel-agent")
		if homeDir, err := os.UserHomeDir(); err == nil {
			dataDirs = append(dataDirs, filepath.Join(homeDir, ".config", "beszel"))
		}
	}
	return testDataDirs(dataDirs)
}

func testDataDirs(paths []string) (string, error) {
	// first check if the directory exists and is writable
	for _, path := range paths {
		if valid, _ := isValidDataDir(path, false); valid {
			return path, nil
		}
	}
	// if the directory doesn't exist, try to create it
	for _, path := range paths {
		exists, _ := directoryExists(path)
		if exists {
			continue
		}

		if err := os.MkdirAll(path, 0755); err != nil {
			continue
		}

		// Verify the created directory is actually writable
		writable, _ := directoryIsWritable(path)
		if !writable {
			continue
		}

		return path, nil
	}

	return "", errors.New("data directory not found")
}

func isValidDataDir(path string, createIfNotExists bool) (bool, error) {
	exists, err := directoryExists(path)
	if err != nil {
		return false, err
	}

	if !exists {
		if !createIfNotExists {
			return false, nil
		}
		if err = os.MkdirAll(path, 0755); err != nil {
			return false, err
		}
	}

	// Always check if the directory is writable
	writable, err := directoryIsWritable(path)
	if err != nil {
		return false, err
	}
	return writable, nil
}

// directoryExists checks if a directory exists
func directoryExists(path string) (bool, error) {
	// Check if directory exists
	stat, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	if !stat.IsDir() {
		return false, fmt.Errorf("%s is not a directory", path)
	}
	return true, nil
}

// directoryIsWritable tests if a directory is writable by creating and removing a temporary file
func directoryIsWritable(path string) (bool, error) {
	testFile := filepath.Join(path, ".write-test")
	file, err := os.Create(testFile)
	if err != nil {
		return false, err
	}
	defer file.Close()
	defer os.Remove(testFile)
	return true, nil
}
