package router

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"text/template"

	"github.com/kardianos/service"

	"github.com/Control-D-Inc/ctrld/internal/router/nvram"
)

const tomatoNvramScriptWanupKey = "script_wanup"

type tomatoSvc struct {
	i        service.Interface
	platform string
	*service.Config
}

func newTomatoService(i service.Interface, platform string, c *service.Config) (service.Service, error) {
	s := &tomatoSvc{
		i:        i,
		platform: platform,
		Config:   c,
	}
	return s, nil
}

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

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

func (s *tomatoSvc) configPath() string {
	bin := s.Config.Executable
	if bin == "" {
		path, err := os.Executable()
		if err != nil {
			return ""
		}
		bin = path
	}
	return bin + ".startup"
}

func (s *tomatoSvc) template() *template.Template {
	return template.Must(template.New("").Parse(tomatoSvcScript))
}

func (s *tomatoSvc) Install() error {
	exePath, err := os.Executable()
	if err != nil {
		return err
	}

	if !strings.HasPrefix(exePath, "/jffs/") {
		return errors.New("could not install service outside /jffs")
	}
	if _, err := nvram.Run("set", "jffs2_on=1"); err != nil {
		return err
	}
	if _, err := nvram.Run("commit"); err != nil {
		return err
	}

	confPath := s.configPath()
	if _, err := os.Stat(confPath); err == nil {
		return fmt.Errorf("already installed: %s", confPath)
	}

	var to = &struct {
		*service.Config
		Path string
	}{
		s.Config,
		exePath,
	}

	f, err := os.Create(confPath)
	if err != nil {
		return fmt.Errorf("os.Create: %w", err)
	}
	defer f.Close()

	if err := s.template().Execute(f, to); err != nil {
		return fmt.Errorf("s.template.Execute: %w", err)
	}

	if err = os.Chmod(confPath, 0755); err != nil {
		return fmt.Errorf("os.Chmod: startup script: %w", err)
	}

	nvramKvMap := map[string]string{
		tomatoNvramScriptWanupKey: "", // script to start ctrld, filled by tomatoSvc.Install method.
	}
	old, err := nvram.Run("get", tomatoNvramScriptWanupKey)
	if err != nil {
		return fmt.Errorf("nvram: %w", err)
	}
	nvramKvMap[tomatoNvramScriptWanupKey] = strings.Join([]string{old, s.configPath() + " start"}, "\n")
	if err := nvram.SetKV(nvramKvMap, nvram.CtrldInstallKey); err != nil {
		return err
	}
	return nil
}

func (s *tomatoSvc) Uninstall() error {
	if err := os.Remove(s.configPath()); err != nil {
		return fmt.Errorf("os.Remove: %w", err)
	}
	nvramKvMap := map[string]string{
		tomatoNvramScriptWanupKey: "", // script to start ctrld, filled by tomatoSvc.Install method.
	}
	// Restore old configs.
	if err := nvram.Restore(nvramKvMap, nvram.CtrldInstallKey); err != nil {
		return err
	}
	return nil
}

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

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

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

	if interactice, _ := isInteractive(); !interactice {
		signal.Ignore(syscall.SIGHUP)
	}

	var sigChan = make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt)
	<-sigChan

	return s.i.Stop(s)
}

func (s *tomatoSvc) Status() (service.Status, error) {
	if _, err := os.Stat(s.configPath()); os.IsNotExist(err) {
		return service.StatusUnknown, service.ErrNotInstalled
	}
	out, err := exec.Command(s.configPath(), "status").CombinedOutput()
	if err != nil {
		return service.StatusUnknown, err
	}
	switch string(bytes.TrimSpace(out)) {
	case "running":
		return service.StatusRunning, nil
	default:
		return service.StatusStopped, nil
	}
}

func (s *tomatoSvc) Start() error {
	return exec.Command(s.configPath(), "start").Run()
}

func (s *tomatoSvc) Stop() error {
	return exec.Command(s.configPath(), "stop").Run()
}

func (s *tomatoSvc) Restart() error {
	return exec.Command(s.configPath(), "restart").Run()
}

// https://wiki.freshtomato.org/doku.php/freshtomato_zerotier?s[]=%2Aservice%2A
const tomatoSvcScript = `#!/bin/sh
 

NAME="{{.Name}}"
CMD="{{.Path}}{{range .Arguments}} {{.}}{{end}}"
LOG_FILE="/var/log/${NAME}.log"
PID_FILE="/tmp/$NAME.pid"
 
 
alias elog="logger -t $NAME -s"
 
 
COND=$1
[ $# -eq 0 ] && COND="start"
 
get_pid() {
  cat "$PID_FILE"
}

is_running() {
  [ -f "$PID_FILE" ] && ps | grep -q "^ *$(get_pid) "
}

start() {
  if is_running; then
    elog "$NAME is already running."
    exit 1
  fi
  elog "Starting $NAME Services: "
  $CMD &
  echo $! > "$PID_FILE"
  chmod 600 "$PID_FILE"
  if is_running; then
    elog "succeeded."
  else
    elog "failed."
  fi
}
 
 
stop() {
  if ! is_running; then
    elog "$NAME is not running."
    exit 0
  fi
  elog "Shutting down $NAME Services: "
  kill -SIGTERM "$(get_pid)"
  for _ in 1 2 3 4 5; do
    if ! is_running; then
      if [ -f "$pid_file" ]; then
        rm "$pid_file"
      fi
      return 0
    fi
    printf "."
    sleep 2
  done
  if ! is_running; then
    elog "succeeded."
  else
    elog "failed."
  fi
}
 
 
do_restart() {
  stop
  start
}


do_status() {
  if ! is_running; then
    echo "stopped"
  else
    echo "running"
  fi
}
 
 
case "$COND" in
start)
  start
  ;;
stop)
  stop
  ;;
restart)
  do_restart
  ;;
status)
  do_status
  ;;
*)
  elog "Usage: $0 (start|stop|restart|status)"
  ;;
esac
exit 0
`
