// The app program is a proof-of-concept frontend for cmd/hakurei.
//
// This program is not covered by the compatibility promise. The command line
// interface and configuration syntax may change at any time.
package main

import (
	"context"
	"errors"
	"io"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"syscall"

	"hakurei.app/check"
	"hakurei.app/command"
	"hakurei.app/fhs"
	"hakurei.app/hst"
	"hakurei.app/message"
)

func main() {
	log.SetFlags(0)
	log.SetPrefix("app: ")
	msg := message.New(log.Default())

	ctx, stop := signal.NotifyContext(context.Background(),
		syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
	defer stop()

	var (
		flagVerbose  bool
		flagBase     string
		flagInsecure bool

		base, template, initial *check.Absolute
	)
	c := command.New(os.Stderr, log.Printf, "app", func([]string) (err error) {
		msg.SwapVerbose(flagVerbose)
		flagBase = os.ExpandEnv(flagBase)
		if flagBase == "" {
			flagBase = "state"
		}
		if flagBase, err = filepath.Abs(flagBase); err != nil {
			return
		} else if base, err = check.NewAbs(flagBase); err != nil {
			return
		}

		template = base.Append("template")
		initial = base.Append("initial")
		return
	}).Flag(
		&flagVerbose,
		"v", command.BoolFlag(false),
		"Increase log verbosity",
	).Flag(
		&flagBase,
		"d", command.StringFlag("$ROSA_APP_PATH"),
		"Configuration and state directory",
	).Flag(
		&flagInsecure,
		"insecure", command.BoolFlag(false),
		"Allow use of insecure compatibility options",
	)

	{
		var (
			flagShell string
			flagHome  string
		)
		c.NewCommand(
			"enter", "Enter mutable state template",
			func(args []string) error {
				if len(args) != 1 {
					return list(template, true)
				}

				config := hst.Config{
					ID: "app.hakurei.mutable." + args[0],
					Container: &hst.ContainerConfig{
						Hostname: args[0] + "-mutable",
						Filesystem: []hst.FilesystemConfigJSON{
							{FilesystemConfig: &hst.FSOverlay{
								Target: fhs.AbsRoot,
								Lower:  []*check.Absolute{initial},
								Upper:  template.Append(args[0]),
								Work:   base.Append("work", args[0]),
							}},
							{FilesystemConfig: &hst.FSEphemeral{
								Target: fhs.AbsTmp,
								Write:  true,
								Perm:   0755,
							}},
						},
						Username: "chronos",
						Flags: hst.FNoPlace |
							hst.FMultiarch |
							hst.FDevel |
							hst.FUserns |
							hst.FHostNet |
							hst.FTty,
					},
				}

				if a, err := check.NewAbs(flagShell); err != nil {
					return err
				} else {
					config.Container.Shell = a
					config.Container.Path = a
					config.Container.Args = []string{
						"-" + filepath.Base(flagShell),
					}
				}

				if a, err := check.NewAbs(flagHome); err != nil {
					return err
				} else {
					config.Container.Home = a
				}

				remove, err := acquireTemplate(base, args[0])
				if err != nil {
					return err
				}
				err = run(ctx, msg, false, &config)
				return errors.Join(err, remove())
			},
		).Flag(
			&flagShell,
			"shell", command.StringFlag("/bin/zsh"),
			"Shell program within container",
		).Flag(
			&flagHome,
			"home", command.StringFlag("/home/chronos"),
			"Home directory within container",
		)
	}

	{
		var (
			flagCommand string
		)
		c.NewCommand(
			"run", "Start the named application",
			func(args []string) error {
				if len(args) < 1 {
					return list(base.Append("app"), false)
				}

				var config *hst.Config
				var r io.Reader
				f, err := os.Open(base.Append("app", args[0]).String())
				if err != nil {
					return err
				}
				r = f

				var common *os.File
				if common, err = os.Open(base.Append("common").String()); err != nil {
					if !errors.Is(err, os.ErrNotExist) {
						_ = f.Close()
						return err
					}
				} else {
					r = io.MultiReader(f, common)
				}

				var name string
				config, err = parse(args[0], base, r, &name)
				if closeErr := f.Close(); err == nil {
					err = closeErr
				}
				if common != nil {
					if closeErr := common.Close(); err == nil {
						err = closeErr
					}
				}
				if err != nil {
					return err
				}

				if flagCommand != "" {
					config.Container.Args[2] = flagCommand
				}

				if err = enterTemplate(base, name); err != nil {
					return err
				}
				return run(ctx, msg, flagInsecure, config, args[1:]...)
			},
		).
			Flag(
				&flagCommand,
				"command", command.StringFlag(""),
				"Override configured command",
			)
	}

	c.MustParse(os.Args[1:], func(err error) {
		if e, ok := errors.AsType[*exec.ExitError](err); ok && e != nil {
			os.Exit(e.ExitCode())
		}

		if w, ok := err.(interface{ Unwrap() []error }); !ok {
			var m string
			m, ok = message.GetMessage(err)
			if !ok {
				log.Fatal(err)
				return
			}
			log.Fatal(m)
		} else {
			errs := w.Unwrap()
			for i, e := range errs {
				if i == len(errs)-1 {
					log.Fatal(e)
				}
				log.Println(e)
			}
		}
	})
}
