package identity

import (
	"errors"
	"fmt"
	"strings"
)

var (
	ErrUnbalancedTemplatingCharacter = errors.New("unbalanced templating characters")
	ErrNoEntityAttachedToToken       = errors.New("string contains entity template directives but no entity was provided")
	ErrNoGroupsAttachedToToken       = errors.New("string contains groups template directives but no groups were provided")
	ErrTemplateValueNotFound         = errors.New("no value could be found for one of the template directives")
)

type PopulateStringInput struct {
	ValidityCheckOnly bool
	String            string
	Entity            *Entity
	Groups            []*Group
}

func PopulateString(p *PopulateStringInput) (bool, string, error) {
	if p == nil {
		return false, "", errors.New("nil input")
	}

	if p.String == "" {
		return false, "", nil
	}

	var subst bool
	splitStr := strings.Split(p.String, "{{")

	if len(splitStr) >= 1 {
		if strings.Index(splitStr[0], "}}") != -1 {
			return false, "", ErrUnbalancedTemplatingCharacter
		}
		if len(splitStr) == 1 {
			return false, p.String, nil
		}
	}

	var b strings.Builder
	if !p.ValidityCheckOnly {
		b.Grow(2 * len(p.String))
	}

	for i, str := range splitStr {
		if i == 0 {
			if !p.ValidityCheckOnly {
				b.WriteString(str)
			}
			continue
		}
		splitPiece := strings.Split(str, "}}")
		switch len(splitPiece) {
		case 2:
			subst = true
			if !p.ValidityCheckOnly {
				tmplStr, err := performTemplating(strings.TrimSpace(splitPiece[0]), p.Entity, p.Groups)
				if err != nil {
					return false, "", err
				}
				b.WriteString(tmplStr)
				b.WriteString(splitPiece[1])
			}
		default:
			return false, "", ErrUnbalancedTemplatingCharacter
		}
	}

	return subst, b.String(), nil
}

func performTemplating(input string, entity *Entity, groups []*Group) (string, error) {
	performAliasTemplating := func(trimmed string, alias *Alias) (string, error) {
		switch {
		case trimmed == "id":
			return alias.ID, nil
		case trimmed == "name":
			if alias.Name == "" {
				return "", ErrTemplateValueNotFound
			}
			return alias.Name, nil
		case strings.HasPrefix(trimmed, "metadata."):
			val, ok := alias.Metadata[strings.TrimPrefix(trimmed, "metadata.")]
			if !ok {
				return "", ErrTemplateValueNotFound
			}
			return val, nil
		}

		return "", ErrTemplateValueNotFound
	}

	performEntityTemplating := func(trimmed string) (string, error) {
		switch {
		case trimmed == "id":
			return entity.ID, nil
		case trimmed == "name":
			if entity.Name == "" {
				return "", ErrTemplateValueNotFound
			}
			return entity.Name, nil
		case strings.HasPrefix(trimmed, "metadata."):
			val, ok := entity.Metadata[strings.TrimPrefix(trimmed, "metadata.")]
			if !ok {
				return "", ErrTemplateValueNotFound
			}
			return val, nil
		case strings.HasPrefix(trimmed, "aliases."):
			split := strings.SplitN(strings.TrimPrefix(trimmed, "aliases."), ".", 2)
			if len(split) != 2 {
				return "", errors.New("invalid alias selector")
			}
			var found *Alias
			for _, alias := range entity.Aliases {
				if split[0] == alias.MountAccessor {
					found = alias
					break
				}
			}
			if found == nil {
				return "", errors.New("alias not found")
			}
			return performAliasTemplating(split[1], found)
		}

		return "", ErrTemplateValueNotFound
	}

	performGroupsTemplating := func(trimmed string) (string, error) {
		var ids bool

		selectorSplit := strings.SplitN(trimmed, ".", 2)
		switch {
		case len(selectorSplit) != 2:
			return "", errors.New("invalid groups selector")
		case selectorSplit[0] == "ids":
			ids = true
		case selectorSplit[0] == "names":
		default:
			return "", errors.New("invalid groups selector")
		}
		trimmed = selectorSplit[1]

		accessorSplit := strings.SplitN(trimmed, ".", 2)
		if len(accessorSplit) != 2 {
			return "", errors.New("invalid groups accessor")
		}
		var found *Group
		for _, group := range groups {
			compare := group.Name
			if ids {
				compare = group.ID
			}

			if compare == accessorSplit[0] {
				found = group
				break
			}
		}

		if found == nil {
			return "", fmt.Errorf("entity is not a member of group %q", accessorSplit[0])
		}

		trimmed = accessorSplit[1]

		switch {
		case trimmed == "id":
			return found.ID, nil
		case trimmed == "name":
			if found.Name == "" {
				return "", ErrTemplateValueNotFound
			}
			return found.Name, nil
		case strings.HasPrefix(trimmed, "metadata."):
			val, ok := found.Metadata[strings.TrimPrefix(trimmed, "metadata.")]
			if !ok {
				return "", ErrTemplateValueNotFound
			}
			return val, nil
		}

		return "", ErrTemplateValueNotFound
	}

	switch {
	case strings.HasPrefix(input, "identity.entity."):
		if entity == nil {
			return "", ErrNoEntityAttachedToToken
		}
		return performEntityTemplating(strings.TrimPrefix(input, "identity.entity."))

	case strings.HasPrefix(input, "identity.groups."):
		if len(groups) == 0 {
			return "", ErrNoGroupsAttachedToToken
		}
		return performGroupsTemplating(strings.TrimPrefix(input, "identity.groups."))
	}

	return "", ErrTemplateValueNotFound
}
