package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"

	"hakurei.app/check"
	"hakurei.app/ext"
	"hakurei.app/fhs"
	"hakurei.app/hst"
)

// parsePair parses a NUL-delimited quoted paths pair.
func parsePair(s string) (source, target *check.Absolute, err error) {
	var p string
	if p, err = strconv.Unquote(s); err != nil {
		return
	}
	_source, _target, ok := strings.Cut(p, "\x00")
	if source, err = check.NewAbs(_source); err != nil {
		return
	}
	if !ok {
		return
	}
	target, err = check.NewAbs(_target)
	return
}

// parse decodes a high-level configuration stream and returns its
// corresponding [hst.Config].
func parse(
	id string,
	base *check.Absolute,
	r io.Reader,
	templateP *string,
) (*hst.Config, error) {
	shell := fhs.AbsRoot.Append("bin", "zsh")
	home := hst.AbsPrivateTmp.Append("home")

	root := hst.FSOverlay{
		Target: fhs.AbsRoot,
		Lower:  []*check.Absolute{base.Append("initial")},
	}
	c := hst.Config{
		ID:          id,
		Enablements: new(hst.Enablements),

		SessionBus: &hst.BusConfig{
			Own: []string{
				id + ".*",
				"org.mpris.MediaPlayer2." + id + ".*",
			},
			Filter: true,
		},
		SystemBus: &hst.BusConfig{Filter: true},

		Container: &hst.ContainerConfig{
			Env: make(map[string]string),
			Filesystem: []hst.FilesystemConfigJSON{
				{FilesystemConfig: &root},
				{FilesystemConfig: &hst.FSBind{
					Target: home,
					Source: base.Append("state", id),
					Write:  true,
					Ensure: true,
				}},

				{FilesystemConfig: &hst.FSEphemeral{
					Target: fhs.AbsVar.Append("tmp"),
					Write:  true,
					Perm:   01777,
				}},
			},

			Username: "chronos",
			Shell:    shell,
			Home:     home,
			Path:     shell,
			Args:     []string{"zsh", "-c"},

			Flags: hst.FCoverRun,
		},
	}

	s := bufio.NewScanner(r)
	scanOnce := func() error {
		if s.Scan() {
			return nil
		}
		if err := s.Err(); err != nil {
			return err
		}
		return io.ErrUnexpectedEOF
	}

	if err := scanOnce(); err != nil {
		return nil, err
	}
	if template, identity, ok := strings.Cut(s.Text(), ":"); !ok {
		return nil, io.ErrUnexpectedEOF
	} else if v, err := strconv.Atoi(identity); err != nil {
		return nil, err
	} else {
		if templateP != nil {
			*templateP = template
		}
		c.Identity = v
		root.Upper = base.Append("template", template)
	}

	if err := scanOnce(); err != nil {
		return nil, err
	}
	c.Container.Args = append(c.Container.Args, s.Text(), "")

	var flagInteractive, flagGPU, flagSystemBus bool
	flags := map[string]*bool{
		"interactive": &flagInteractive,
		"gpu":         &flagGPU,
		"system_bus":  &flagSystemBus,
	}

	for s.Scan() {
		key, value, ok := strings.Cut(s.Text(), " ")
		if key != "" && key[0] == ';' {
			continue
		}

		if !ok {
			if key == "" {
				continue
			}

			var p *bool
			if p, ok = flags[key]; ok {
				*p = true
				continue
			}

			switch key {
			case "wayland":
				*c.Enablements |= hst.EWayland
			case "x11":
				*c.Enablements |= hst.EX11
			case "dbus":
				*c.Enablements |= hst.EDBus
			case "pipewire":
				*c.Enablements |= hst.EPipeWire

			case "multiarch":
				c.Container.Flags |= hst.FMultiarch
			case "devel":
				c.Container.Flags |= hst.FDevel
			case "userns":
				c.Container.Flags |= hst.FUserns
			case "net":
				c.Container.Flags |= hst.FHostNet
			case "abstract":
				c.Container.Flags |= hst.FHostAbstract
			case "tty":
				c.Container.Flags |= hst.FTty
			case "mapuid":
				c.Container.Flags |= hst.FMapRealUID
			case "device":
				c.Container.Flags |= hst.FDevice

			case "share_runtime":
				c.Container.Flags |= hst.FShareRuntime
			case "share_tmpdir":
				c.Container.Flags |= hst.FShareTmpdir

			default:
				return nil, fmt.Errorf("invalid flag %q", key)
			}

			continue
		}

		switch key {
		case "username":
			c.Container.Username = value
			continue

		case "hostname":
			c.Container.Hostname = value
			continue

		case "group":
			c.Groups = append(c.Groups, value)
			continue

		case "sched_policy":
			if err := c.SchedPolicy.UnmarshalText([]byte(value)); err != nil {
				return nil, err
			}
			continue

		case "sched_priority":
			v, err := strconv.Atoi(value)
			if err != nil {
				return nil, err
			}
			c.SchedPriority = ext.Int(v)
			continue

		case "insecure":
			switch value {
			case "pipewire":
				*c.Enablements |= hst.EPipeWire
				c.DirectPipeWire = true
				continue

			default:
				return nil, fmt.Errorf("invalid insecure flag %q", value)
			}

		case "env":
			if key, value, ok = strings.Cut(value, "="); !ok {
				return nil, fmt.Errorf("invalid environment %q", key)
			}
			c.Container.Env[key] = value
			continue

		case "ro":
			source, target, err := parsePair(value)
			if err != nil {
				return nil, err
			}
			c.Container.Filesystem = append(c.Container.Filesystem,
				hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
					Target: target,
					Source: source,
				}},
			)
			continue

		case "ro+":
			source, target, err := parsePair(value)
			if err != nil {
				return nil, err
			}
			c.Container.Filesystem = append(c.Container.Filesystem,
				hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
					Target:   target,
					Source:   source,
					Optional: true,
				}},
			)
			continue

		case "rw":
			source, target, err := parsePair(value)
			if err != nil {
				return nil, err
			}
			c.Container.Filesystem = append(c.Container.Filesystem,
				hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
					Target: target,
					Source: source,
					Write:  true,
				}},
			)
			continue

		case "dev":
			source, target, err := parsePair(value)
			if err != nil {
				return nil, err
			}
			c.Container.Filesystem = append(c.Container.Filesystem,
				hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
					Target: target,
					Source: source,
					Device: true,
				}},
			)
			continue

		case "own":
			c.SessionBus.Own = append(c.SessionBus.Own, value)
			continue
		case "own_system":
			c.SystemBus.Own = append(c.SystemBus.Own, value)
			continue

		case "talk":
			c.SessionBus.Talk = append(c.SessionBus.Talk, value)
			continue
		case "talk_system":
			c.SystemBus.Talk = append(c.SystemBus.Talk, value)
			continue

		default:
			return nil, fmt.Errorf("invalid key %q", key)
		}
	}
	if err := s.Err(); err != nil {
		return nil, err
	}

	if flagInteractive {
		c.Container.Args[1] += "i"
	}

	if flagGPU {
		c.Container.Filesystem = append(c.Container.Filesystem, []hst.FilesystemConfigJSON{
			{FilesystemConfig: &hst.FSBind{
				Source:   fhs.AbsDev.Append("dri"),
				Device:   true,
				Optional: true,
			}},
		}...)
	}

	if !flagSystemBus {
		c.SystemBus = nil
	}

	if c.Container.Flags&hst.FShareTmpdir == 0 &&
		(c.Enablements.Unwrap()&hst.EX11 == 0 ||
			c.Container.Flags&(hst.FHostNet|hst.FHostAbstract) ==
				hst.FHostNet|hst.FHostAbstract) {
		c.Container.Filesystem = append(c.Container.Filesystem,
			hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{
				Target: fhs.AbsTmp,
				Write:  true,
				Perm:   01777,
			}},
		)
	}

	return &c, nil
}

// list prints names of non-hidden entries in pathname.
func list(pathname *check.Absolute, dir bool) error {
	dents, err := os.ReadDir(pathname.String())
	if err != nil {
		return err
	}
	for _, dent := range dents {
		name := dent.Name()
		if (dent.IsDir() != dir) || (len(name) > 0 && name[0] == '.') {
			continue
		}
		fmt.Println(dent.Name())
	}
	return nil
}
