package service

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"regexp"
	"syscall"
	"text/template"
	"time"
)

func isOpenRC() bool {
	if _, err := exec.LookPath("openrc-init"); err == nil {
		return true
	}
	if _, err := os.Stat("/etc/inittab"); err == nil {
		filerc, err := os.Open("/etc/inittab")
		if err != nil {
			return false
		}
		defer filerc.Close()

		buf := new(bytes.Buffer)
		buf.ReadFrom(filerc)
		contents := buf.String()

		re := regexp.MustCompile(`::sysinit:.*openrc.*sysinit`)
		matches := re.FindStringSubmatch(contents)
		if len(matches) > 0 {
			return true
		}
		return false
	}
	return false
}

type openrc struct {
	i        Interface
	platform string
	*Config
}

func (s *openrc) String() string {
	if len(s.DisplayName) > 0 {
		return s.DisplayName
	}
	return s.Name
}

func (s *openrc) Platform() string {
	return s.platform
}

func (s *openrc) template() *template.Template {
	customScript := s.Option.string(optionOpenRCScript, "")

	if customScript != "" {
		return template.Must(template.New("").Funcs(tf).Parse(customScript))
	}
	return template.Must(template.New("").Funcs(tf).Parse(openRCScript))
}

func newOpenRCService(i Interface, platform string, c *Config) (Service, error) {
	s := &openrc{
		i:        i,
		platform: platform,
		Config:   c,
	}
	return s, nil
}

var errNoUserServiceOpenRC = errors.New("user services are not supported on OpenRC")

func (s *openrc) configPath() (cp string, err error) {
	if s.Option.bool(optionUserService, optionUserServiceDefault) {
		err = errNoUserServiceOpenRC
		return
	}
	cp = "/etc/init.d/" + s.Config.Name
	return
}

func (s *openrc) Install() error {
	confPath, err := s.configPath()
	if err != nil {
		return err
	}
	_, err = os.Stat(confPath)
	if err == nil {
		return fmt.Errorf("Init already exists: %s", confPath)
	}

	f, err := os.Create(confPath)
	if err != nil {
		return err
	}
	defer f.Close()

	err = os.Chmod(confPath, 0755)
	if err != nil {
		return err
	}

	path, err := s.execPath()
	if err != nil {
		return err
	}

	var to = &struct {
		*Config
		Path         string
		LogDirectory string
	}{
		s.Config,
		path,
		s.Option.string(optionLogDirectory, defaultLogDirectory),
	}

	err = s.template().Execute(f, to)
	if err != nil {
		return err
	}
	// run rc-update
	return s.runAction("add")
}

func (s *openrc) Uninstall() error {
	confPath, err := s.configPath()
	if err != nil {
		return err
	}
	if err := os.Remove(confPath); err != nil {
		return err
	}
	return s.runAction("delete")
}

func (s *openrc) Logger(errs chan<- error) (Logger, error) {
	if system.Interactive() {
		return ConsoleLogger, nil
	}
	return s.SystemLogger(errs)
}

func (s *openrc) SystemLogger(errs chan<- error) (Logger, error) {
	return newSysLogger(s.Name, errs)
}

func (s *openrc) Run() (err error) {
	err = s.i.Start(s)
	if err != nil {
		return err
	}

	s.Option.funcSingle(optionRunWait, func() {
		var sigChan = make(chan os.Signal, 3)
		signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt)
		<-sigChan
	})()

	return s.i.Stop(s)
}

func (s *openrc) Status() (Status, error) {
	// rc-service uses the errno library for its exit codes:
	// errno 0 = service started
	// errno 1 = EPERM 1 Operation not permitted
	// errno 2 = ENOENT 2 No such file or directory
	// errno 3 = ESRCH 3 No such process
	// for more info, see https://man7.org/linux/man-pages/man3/errno.3.html
	_, out, err := runWithOutput("rc-service", s.Name, "status")
	if err != nil {
		if exiterr, ok := err.(*exec.ExitError); ok {
			// The program has exited with an exit code != 0
			exitCode := exiterr.ExitCode()
			switch {
			case exitCode == 1:
				return StatusUnknown, err
			case exitCode == 2:
				return StatusUnknown, ErrNotInstalled
			case exitCode == 3:
				return StatusStopped, nil
			default:
				return StatusUnknown, fmt.Errorf("unknown error: %v - %v", out, err)
			}
		} else {
			return StatusUnknown, err
		}
	}
	return StatusRunning, nil
}

func (s *openrc) Start() error {
	return run("rc-service", s.Name, "start")
}

func (s *openrc) Stop() error {
	return run("rc-service", s.Name, "stop")
}

func (s *openrc) Restart() error {
	err := s.Stop()
	if err != nil {
		return err
	}
	time.Sleep(50 * time.Millisecond)
	return s.Start()
}

func (s *openrc) runAction(action string) error {
	return s.run(action, s.Name)
}

func (s *openrc) run(action string, args ...string) error {
	return run("rc-update", append([]string{action}, args...)...)
}

const openRCScript = `#!/sbin/openrc-run
supervisor=supervise-daemon
name="{{.DisplayName}}"
description="{{.Description}}"
command={{.Path|cmdEscape}}
{{- if .Arguments }}
command_args="{{range .Arguments}}{{.}} {{end}}"
{{- end }}
name=$(basename $(readlink -f $command))
supervise_daemon_args="--stdout {{.LogDirectory}}/${name}.log --stderr {{.LogDirectory}}/${name}.err"

{{range $k, $v := .EnvVars -}}
export {{$k}}={{$v}}
{{end -}}

{{- if .Dependencies }}
depend() {
{{- range $i, $dep := .Dependencies}} 
{{"\t"}}{{$dep}}{{end}}
}
{{- end}}
`
