//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"time"

	"git.rootprojects.org/root/go-serviceman/manager"
	"git.rootprojects.org/root/go-serviceman/runner"
	"git.rootprojects.org/root/go-serviceman/service"
)

var GitRev = "000000000"
var GitVersion = "v0.0.0"
var GitTimestamp = time.Now().Format(time.RFC3339)

func usage() {
	fmt.Println("Usage: serviceman add ./foo-app -- --foo-arg")
	fmt.Println("Usage: serviceman run --config ./foo-app.json")
}

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintf(os.Stderr, "Too few arguments: %s\n", strings.Join(os.Args, " "))
		usage()
		os.Exit(1)
	}

	top := os.Args[1]
	os.Args = append(os.Args[:1], os.Args[2:]...)
	switch top {
	case "version":
		fmt.Println(GitVersion, GitTimestamp, GitRev)
	case "add":
		add()
	case "run":
		run()
	default:
		fmt.Fprintf(os.Stderr, "Unknown argument %s\n", top)
		usage()
		os.Exit(1)
	}
}

func add() {
	conf := &service.Service{
		Restart: true,
	}

	args := []string{}
	for i := range os.Args {
		if "--" == os.Args[i] {
			if len(os.Args) > i+1 {
				args = os.Args[i+1:]
			}
			os.Args = os.Args[:i]
			break
		}
	}
	conf.Argv = args

	force := false
	forUser := false
	forSystem := false
	flag.StringVar(&conf.Title, "title", "", "a human-friendly name for the service")
	flag.StringVar(&conf.Desc, "desc", "", "a human-friendly description of the service (ex: Foo App)")
	flag.StringVar(&conf.Name, "name", "", "a computer-friendly name for the service (ex: foo-app)")
	flag.StringVar(&conf.URL, "url", "", "the documentation on home page of the service")
	//flag.StringVar(&conf.Workdir, "workdir", "", "the directory in which the service should be started")
	flag.StringVar(&conf.ReverseDNS, "rdns", "", "a plist-friendly Reverse DNS name for launchctl (ex: com.example.foo-app)")
	flag.BoolVar(&forSystem, "system", false, "attempt to add system service as an unprivileged/unelevated user")
	flag.BoolVar(&forUser, "user", false, "add user space / user mode service even when admin/root/sudo/elevated")
	flag.BoolVar(&force, "force", false, "if the interpreter or executable doesn't exist, or things don't make sense, try anyway")
	flag.StringVar(&conf.User, "username", "", "run the service as this user")
	flag.StringVar(&conf.Group, "groupname", "", "run the service as this group")
	flag.BoolVar(&conf.PrivilegedPorts, "cap-net-bind", false, "this service should have access to privileged ports")
	flag.Parse()
	args = flag.Args()

	if forUser && forSystem {
		fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
		os.Exit(1)
		return
	}
	if forUser {
		conf.System = false
	} else if forSystem {
		conf.System = true
	} else {
		conf.System = manager.IsPrivileged()
	}

	n := len(args)
	if 0 == n {
		fmt.Println("Usage: serviceman add ./foo-app -- --foo-arg")
		os.Exit(2)
		return
	}

	execpath, err := manager.WhereIs(args[0])
	if nil != err {
		fmt.Fprintf(os.Stderr, "Error: '%s' could not be found.\n", args[0])
		if !force {
			os.Exit(3)
			return
		}
	} else {
		args[0] = execpath
	}
	conf.Exec = args[0]
	args = args[1:]

	if n >= 2 {
		conf.Interpreter = conf.Exec
		conf.Exec = args[0]
		conf.Argv = append(args[1:], conf.Argv...)
	}

	conf.Normalize(force)

	//fmt.Printf("\n%#v\n\n", conf)

	err = manager.Install(conf)
	if nil != err {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		fmt.Fprintf(os.Stderr, "Use 'sudo' to add service as a privileged system service.\n")
		fmt.Fprintf(os.Stderr, "Use '--user' to add service as an user service.\n")
	}
}

func run() {
	var confpath string
	var daemonize bool
	flag.StringVar(&confpath, "config", "", "path to a config file to run")
	flag.BoolVar(&daemonize, "daemon", false, "spawn a child process that lives in the background, and exit")
	flag.Parse()

	if "" == confpath {
		fmt.Fprintf(os.Stderr, "%s", strings.Join(flag.Args(), " "))
		fmt.Fprintf(os.Stderr, "--config /path/to/config.json is required\n")
		usage()
		os.Exit(1)
	}

	b, err := ioutil.ReadFile(confpath)
	if nil != err {
		fmt.Fprintf(os.Stderr, "Couldn't read config file: %s\n", err)
		os.Exit(400)
	}

	s := &service.Service{}
	err = json.Unmarshal(b, s)
	if nil != err {
		fmt.Fprintf(os.Stderr, "Couldn't JSON parse config file: %s\n", err)
		os.Exit(400)
	}

	m := map[string]interface{}{}
	err = json.Unmarshal(b, &m)
	if nil != err {
		fmt.Fprintf(os.Stderr, "Couldn't JSON parse config file: %s\n", err)
		os.Exit(400)
	}

	// default Restart to true
	if _, ok := m["restart"]; !ok {
		s.Restart = true
	}

	if "" == s.Exec {
		fmt.Fprintf(os.Stderr, "Missing exec\n")
		os.Exit(400)
	}

	s.Normalize(false)
	//fmt.Fprintf(os.Stdout, "Logdir: %s\n", s.Logdir)
	err = os.MkdirAll(s.Logdir, 0755)
	if nil != err {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		return
	}

	if !daemonize {
		//fmt.Fprintf(os.Stdout, "Running %s %s %s\n", s.Interpreter, s.Exec, strings.Join(s.Argv, " "))
		runner.Run(s)
		return
	}

	cmd := exec.Command(os.Args[0], "run", "--config", confpath)
	// for debugging
	/*
		out, err := cmd.CombinedOutput()
		if nil != err {
			fmt.Println(err)
		}
		fmt.Println(string(out))
	*/

	err = cmd.Start()
	if nil != err {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		os.Exit(500)
	}
}
