package app

import (
	"context"
	"fmt"
	"os"
	"os/user"
	"sort"
	"strings"

	"go.sour.is/pkg/mercury"
	"go.sour.is/pkg/rsql"
)

const (
	mercurySource   = "mercury.source.*"
	mercuryPriority = "mercury.priority"
	mercuryHost     = "mercury.host"
	appDotEnviron   = "mercury.environ"
)

func Register(cfg mercury.SpaceMap) {
	for _, c := range cfg {
		c.Tags = append(c.Tags, "RO")
	}
	mercury.Registry.Register("mercury-default", func(s *mercury.Space) any { return &mercuryDefault{} })
	mercury.Registry.Register("mercury-environ", func(s *mercury.Space) any { return &mercuryEnviron{cfg: cfg} })
}

type hasRole interface {
	HasRole(r ...string) bool
}

type mercuryEnviron struct {
	cfg mercury.SpaceMap
}

// Index returns nil
func (app *mercuryEnviron) GetIndex(ctx context.Context, search mercury.NamespaceSearch, _ *rsql.Program) (lis mercury.Config, err error) {

	if search.Match(mercurySource) {
		for _, s := range app.cfg.ToArray() {
			if search.Match(s.Space) {
				lis = append(lis, &mercury.Space{Space: s.Space, Tags: []string{"RO"}})
			}
		}
	}

	if search.Match(mercuryPriority) {
		lis = append(lis, &mercury.Space{Space: mercuryPriority, Tags: []string{"RO"}})
	}

	if search.Match(mercuryHost) {
		lis = append(lis, &mercury.Space{Space: mercuryHost, Tags: []string{"RO"}})
	}

	if search.Match(appDotEnviron) {
		lis = append(lis, &mercury.Space{Space: appDotEnviron, Tags: []string{"RO"}})
	}

	return
}

// Objects returns nil
func (app *mercuryEnviron) GetConfig(ctx context.Context, search mercury.NamespaceSearch, _ *rsql.Program, _ []string) (lis mercury.Config, err error) {
	if search.Match(mercurySource) {
		for _, s := range app.cfg.ToArray() {
			if search.Match(s.Space) {
				lis = append(lis, s)
			}
		}
	}

	if search.Match(mercuryPriority) {
		space := mercury.Space{
			Space: mercuryPriority,
			Tags:  []string{"RO"},
		}

		// for i, key := range mercury.Registry {
		// 	space.List = append(space.List, mercury.Value{
		// 		Space:  appDotPriority,
		// 		Seq:    uint64(i),
		// 		Name:   key.Match,
		// 		Values: []string{fmt.Sprint(key.Priority)},
		// 	})
		// }

		lis = append(lis, &space)
	}

	if search.Match(mercuryHost) {
		if usr, err := user.Current(); err == nil {
			space := mercury.Space{
				Space: mercuryHost,
				Tags:  []string{"RO"},
			}

			hostname, _ := os.Hostname()
			wd, _ := os.Getwd()
			grp, _ := usr.GroupIds()
			space.List = []mercury.Value{
				{
					Space:  mercuryHost,
					Seq:    1,
					Name:   "hostname",
					Values: []string{hostname},
				},
				{
					Space:  mercuryHost,
					Seq:    2,
					Name:   "username",
					Values: []string{usr.Username},
				},
				{
					Space:  mercuryHost,
					Seq:    3,
					Name:   "uid",
					Values: []string{usr.Uid},
				},
				{
					Space:  mercuryHost,
					Seq:    4,
					Name:   "gid",
					Values: []string{usr.Gid},
				},
				{
					Space:  mercuryHost,
					Seq:    5,
					Name:   "display",
					Values: []string{usr.Name},
				},
				{
					Space:  mercuryHost,
					Seq:    6,
					Name:   "home",
					Values: []string{usr.HomeDir},
				},
				{
					Space:  mercuryHost,
					Seq:    7,
					Name:   "groups",
					Values: grp,
				},
				{
					Space:  mercuryHost,
					Seq:    8,
					Name:   "pid",
					Values: []string{fmt.Sprintf("%v", os.Getpid())},
				},
				{
					Space:  mercuryHost,
					Seq:    9,
					Name:   "wd",
					Values: []string{wd},
				},
			}

			lis = append(lis, &space)
		}
	}

	if search.Match(appDotEnviron) {
		env := os.Environ()
		space := mercury.Space{
			Space: appDotEnviron,
			Tags:  []string{"RO"},
		}

		sort.Strings(env)
		for i, s := range env {
			key, val, _ := strings.Cut(s, "=")

			vals := []string{val}
			if strings.Contains(key, "PATH") || strings.Contains(key, "XDG") {
				vals = strings.Split(val, ":")
			}

			space.List = append(space.List, mercury.Value{
				Space:  appDotEnviron,
				Seq:    uint64(i),
				Name:   key,
				Values: vals,
			})
		}
		lis = append(lis, &space)
	}

	return
}

// Rules returns nil
func (mercuryEnviron) GetRules(ctx context.Context, id mercury.Ident) (lis mercury.Rules, err error) {
	u, ok := id.(hasRole)
	if !ok {
		return nil, nil
	}

	if u.HasRole("admin") {
		lis = append(lis,
			mercury.Rule{
				Role:  "read",
				Type:  "NS",
				Match: mercurySource,
			},
			mercury.Rule{
				Role:  "read",
				Type:  "NS",
				Match: mercuryPriority,
			},
			mercury.Rule{
				Role:  "read",
				Type:  "NS",
				Match: mercuryHost,
			},
			mercury.Rule{
				Role:  "read",
				Type:  "NS",
				Match: appDotEnviron,
			},
		)
	}

	return lis, nil
}
