package common

import (
	"errors"
	"os"
	"path/filepath"
	"strings"

	"github.com/bmatcuk/doublestar/v4"
)

var MOCK_CURRENT_DIR string

func GetCurrentDir() string {
	if MOCK_CURRENT_DIR != "" {
		return MOCK_CURRENT_DIR
	}
	ex, _ := os.Executable()
	return filepath.Dir(ex)
}

func GetAbsolutePath(base string, opts ...string) string {
	fullPath := base
	if strings.HasPrefix(base, "/") == false { // relative filepath are relative to the binary
		fullPath = filepath.Join(GetCurrentDir(), base)
	}
	if len(opts) == 0 {
		return fullPath
	}
	return filepath.Join(append([]string{fullPath}, opts...)...)
}

func IsDirectory(path string) bool {
	if path == "" {
		return false
	}
	if path[len(path)-1:] != "/" {
		return false
	}
	return true
}

/*
 * Join 2 path together, result has a file
 */
func JoinPath(base, file string) string {
	filePath := filepath.Join(base, file)
	if strings.HasPrefix(filePath, base) == false {
		return base
	}
	return filePath
}

func EnforceDirectory(path string) string {
	if path == "" {
		return "/"
	} else if path[len(path)-1:] == "/" {
		return path
	}
	return path + "/"
}

func SplitPath(path string) (root string, filename string) {
	if path == "" {
		path = "/"
	}
	if IsDirectory(path) == false {
		filename = filepath.Base(path)
	}
	if root = strings.TrimSuffix(path, filename); root == "" {
		root = "/"
	}
	return root, filename
}

func SafeOsMkdir(path string, mode os.FileMode) error {
	if err := safePath(path); err != nil {
		Log.Debug("common::files safeOsMkdir err[%s] path[%s]", err.Error(), path)
		return ErrFilesystemError
	}
	return processError(os.Mkdir(path, mode))
}

func SafeOsRemove(path string) error {
	if err := safePath(path); err != nil {
		Log.Debug("common::files safeOsRemove err[%s] path[%s]", err.Error(), path)
		return ErrFilesystemError
	}
	return processError(os.Remove(path))
}

func SafeOsRemoveAll(path string) error {
	if err := safePath(path); err != nil {
		Log.Debug("common::files safeOsRemoveAll err[%s] path[%s]", err.Error(), path)
		return ErrFilesystemError
	}
	return processError(os.RemoveAll(path))
}

func SafeOsRename(from string, to string) error {
	if err := safePath(from); err != nil {
		Log.Debug("common::files safeOsRename err[%s] from[%s]", err.Error(), from)
		return ErrFilesystemError
	} else if err := safePath(to); err != nil {
		Log.Debug("common::files safeOsRemove err[%s] to[%s]", err.Error(), to)
		return ErrFilesystemError
	}
	return processError(os.Rename(from, to))
}

func GlobMatch(pattern, name string) bool {
	m, _ := doublestar.Match(pattern, name)
	return m
}

func safePath(path string) error {
	p, err := filepath.EvalSymlinks(path)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) == false {
			return err
		}
		parentPath := filepath.Join(path, "../")
		return safePath(parentPath)
	}
	if p != filepath.Clean(path) {
		Log.Debug("common::files safePath path[%s] p[%s]", path, p)
		return ErrFilesystemError
	}
	return nil
}

func processError(err error) error {
	if err == nil {
		return nil
	}
	if pe, ok := err.(*os.PathError); ok {
		return pe.Err
	}
	if le, ok := err.(*os.LinkError); ok {
		return le.Err
	}
	return err
}
