package util

import (
	"fmt"
	"github.com/alibaba/kt-connect/pkg/common"
	"github.com/rs/zerolog/log"
	"io/ioutil"
	"os"
	"os/user"
	"runtime"
	"strconv"
	"strings"
	"time"

	fs "github.com/fsnotify/fsnotify"
	ps "github.com/mitchellh/go-ps"
)

// TimeDifference seconds between remote and local time
var TimeDifference int64 = 0

// GetDaemonRunning fetch daemon pid if exist
func GetDaemonRunning(componentName string) int {
	files, _ := ioutil.ReadDir(KtPidDir)
	for _, f := range files {
		if strings.HasPrefix(f.Name(), componentName) && strings.HasSuffix(f.Name(), ".pid") {
			from := len(componentName) + 1
			to := len(f.Name()) - len(".pid")
			pid, err := strconv.Atoi(f.Name()[from:to])
			if err == nil && IsProcessExist(pid) {
				return pid
			}
		}
	}
	return -1
}

// IsProcessExist check whether specified process still running
func IsProcessExist(pid int) bool {
	proc, err := ps.FindProcess(pid)
	if proc == nil || err != nil {
		return false
	}
	return strings.Contains(proc.Executable(), "ktctl")
}

// CreateDirIfNotExist create dir
func CreateDirIfNotExist(dir string) error {
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		err = os.MkdirAll(dir, 0755)
		if err != nil {
			return err
		}
	}
	return nil
}

// WritePidFile write pid to file
func WritePidFile(componentName string, ch chan os.Signal) error {
	pidFile := fmt.Sprintf("%s/%s-%d.pid", KtPidDir, componentName, os.Getpid())
	if err := ioutil.WriteFile(pidFile, []byte(fmt.Sprintf("%d", os.Getpid())), 0644); err != nil {
		return err
	}
	go watchPidFile(pidFile, ch)
	return nil
}

func watchPidFile(pidFile string, ch chan os.Signal) {
	watcher, err := fs.NewWatcher()
	if err != nil {
		log.Warn().Err(err).Msgf("Failed to create pid file watcher")
	}
	defer watcher.Close()

	if err = watcher.Add(pidFile); err != nil {
		log.Warn().Err(err).Msgf("Unable to watch pid file")
	}

	for event := range watcher.Events {
		log.Debug().Msgf("Received event %s", event)
		if event.Op & fs.Remove == fs.Remove || event.Op & fs.Rename == fs.Rename {
			log.Info().Msgf("Pid file was removed")
			ch <-os.Interrupt
		}
	}
}

// FixFileOwner set owner to original user when run with sudo
func FixFileOwner(path string) (err error) {
	var uid int
	var gid int
	sudoUid := os.Getenv("SUDO_UID")
	if sudoUid == "" {
		uid = os.Getuid()
	} else {
		if uid, err = strconv.Atoi(sudoUid); err != nil {
			return err
		}
	}
	sudoGid := os.Getenv("SUDO_GID")
	if sudoGid == "" {
		gid = os.Getuid()
	} else {
		if gid, err = strconv.Atoi(sudoGid); err != nil {
			return err
		}
	}
	return os.Chown(path, uid, gid)
}

// GetTime get time with rectification
func GetTime() int64 {
	return time.Now().Unix() + TimeDifference
}

// GetTimestamp get current timestamp
func GetTimestamp() string {
	return strconv.FormatInt(GetTime(), 10)
}

// ParseTimestamp parse timestamp to unix time
func ParseTimestamp(timestamp string) int64 {
	unixTime, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		return -1
	}
	return unixTime
}

// FormattedTime get timestamp to print
func FormattedTime() string {
	return time.Now().Format(common.YyyyMmDdHhMmSs)
}

// GetLocalUserName get current username
func GetLocalUserName() string {
	sudoUser := os.Getenv("SUDO_USER")
	if sudoUser != "" {
		return sudoUser
	}
	u, err := user.Current()
	if err != nil {
		return ""
	}
	return u.Username
}

// IsCmd check running in windows cmd shell
func IsCmd() bool {
	proc, _ := ps.FindProcess(os.Getppid())
	if proc != nil && !strings.Contains(proc.Executable(), "cmd.exe") {
		return false
	}
	return true
}

// IsWindows check runtime is windows
func IsWindows() bool {
	return runtime.GOOS == "windows"
}

// IsMacos check runtime is macos
func IsMacos() bool {
	return runtime.GOOS == "darwin"
}

// IsLinux check runtime is linux
func IsLinux() bool {
	return runtime.GOOS == "linux"
}
