// SPDX-License-Identifier: GPL-3.0-or-later

package main

import (
	"errors"
	"fmt"
	"io/fs"
	"log/slog"
	"os"
	"os/user"
	"path/filepath"
	"strings"

	"github.com/netdata/netdata/go/plugins/logger"
	"github.com/netdata/netdata/go/plugins/pkg/buildinfo"
	"github.com/netdata/netdata/go/plugins/pkg/executable"
	"github.com/netdata/netdata/go/plugins/plugin/go.d/agent"
	"github.com/netdata/netdata/go/plugins/plugin/go.d/cli"
	"github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/multipath"

	"github.com/jessevdk/go-flags"
	"golang.org/x/net/http/httpproxy"

	_ "github.com/netdata/netdata/go/plugins/plugin/go.d/modules"
)

var (
	cygwinBasePath = os.Getenv("NETDATA_CYGWIN_BASE_PATH")

	name        = "go.d"
	userDir     = os.Getenv("NETDATA_USER_CONFIG_DIR")
	stockDir    = os.Getenv("NETDATA_STOCK_CONFIG_DIR")
	varLibDir   = os.Getenv("NETDATA_LIB_DIR")
	lockDir     = os.Getenv("NETDATA_LOCK_DIR")
	watchPath   = os.Getenv("NETDATA_PLUGINS_GOD_WATCH_PATH")
	envLogLevel = os.Getenv("NETDATA_LOG_LEVEL")
)

func init() {
	userDir = handleDirOnWin(userDir)
	stockDir = handleDirOnWin(stockDir)
	varLibDir = handleDirOnWin(varLibDir)
	lockDir = handleDirOnWin(lockDir)
	watchPath = handleDirOnWin(watchPath)
}

func confDir(opts *cli.Option) multipath.MultiPath {
	if len(opts.ConfDir) > 0 {
		return opts.ConfDir
	}

	if userDir != "" || stockDir != "" {
		return multipath.New(userDir, stockDir)
	}

	var dirs []string

	dirs = append(dirs, filepath.Join(executable.Directory, "/../../../../etc/netdata"))

	for _, dir := range []string{
		handleDirOnWin("/etc/netdata"),
		handleDirOnWin("/opt/netdata/etc/netdata"),
	} {
		if isDirExists(dir) {
			dirs = append(dirs, dir)
			break
		}
	}

	dirs = append(dirs, filepath.Join(executable.Directory, "/../../../../usr/lib/netdata/conf.d"))

	for _, dir := range []string{
		handleDirOnWin("/usr/lib/netdata/conf.d"),
		handleDirOnWin("/opt/netdata/usr/lib/netdata/conf.d"),
	} {
		if isDirExists(dir) {
			dirs = append(dirs, dir)
			break
		}
	}

	return multipath.New(dirs...)
}

func modulesConfDir(opts *cli.Option) (mpath multipath.MultiPath) {
	if len(opts.ConfDir) > 0 {
		return opts.ConfDir
	}

	dirs := confDir(opts)
	for _, dir := range dirs {
		mpath = append(mpath, filepath.Join(dir, name))
	}

	return multipath.New(mpath...)
}

func modulesConfSDDir(confDir multipath.MultiPath) (mpath multipath.MultiPath) {
	for _, v := range confDir {
		mpath = append(mpath, filepath.Join(v, "sd"))
	}
	return mpath
}

func watchPaths(opts *cli.Option) []string {
	if watchPath == "" {
		return opts.WatchPath
	}
	return append(opts.WatchPath, watchPath)
}

func stateFile() string {
	if varLibDir == "" {
		return ""
	}
	return filepath.Join(varLibDir, "god-jobs-statuses.json")
}

func init() {
	// https://github.com/netdata/netdata/issues/8949#issuecomment-638294959
	if v := os.Getenv("TZ"); strings.HasPrefix(v, ":") {
		_ = os.Unsetenv("TZ")
	}
}

func main() {
	opts := parseCLI()

	if opts.Version {
		fmt.Printf("go.d.plugin, version: %s\n", buildinfo.Version)
		return
	}

	if envLogLevel != "" {
		logger.Level.SetByName(envLogLevel)
	}

	if opts.Debug {
		logger.Level.Set(slog.LevelDebug)
	}

	dir := modulesConfDir(opts)

	a := agent.New(agent.Config{
		Name:                 name,
		ConfDir:              confDir(opts),
		ModulesConfDir:       dir,
		ModulesConfSDDir:     modulesConfSDDir(dir),
		ModulesConfWatchPath: watchPaths(opts),
		VnodesConfDir:        confDir(opts),
		StateFile:            stateFile(),
		LockDir:              lockDir,
		RunModule:            opts.Module,
		MinUpdateEvery:       opts.UpdateEvery,
	})

	a.Debugf("plugin: name=%s, version=%s", a.Name, buildinfo.Version)
	if u, err := user.Current(); err == nil {
		a.Debugf("current user: name=%s, uid=%s", u.Username, u.Uid)
	}

	cfg := httpproxy.FromEnvironment()
	a.Infof("env HTTP_PROXY '%s', HTTPS_PROXY '%s'", cfg.HTTPProxy, cfg.HTTPSProxy)

	a.Run()
}

func parseCLI() *cli.Option {
	opt, err := cli.Parse(os.Args)
	if err != nil {
		var flagsErr *flags.Error
		if errors.As(err, &flagsErr) && errors.Is(flagsErr.Type, flags.ErrHelp) {
			os.Exit(0)
		} else {
			os.Exit(1)
		}
	}
	return opt
}

func isDirExists(dir string) bool {
	fi, err := os.Stat(dir)
	if err == nil {
		return fi.Mode().IsDir()
	}
	return !errors.Is(err, fs.ErrNotExist)
}

func handleDirOnWin(path string) string {
	base := cygwinBasePath

	// TODO: temp workaround for debug mode
	if base == "" && strings.HasPrefix(executable.Directory, "C:\\msys64") {
		base = "C:\\msys64"
	}

	if base == "" || !strings.HasPrefix(path, "/") {
		return path
	}

	return filepath.Join(base, path)
}
