package completer

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/c-bata/go-prompt"
	"github.com/openconfig/goyang/pkg/yang"
)

var (
	// YangSchemaCompletionSeperator is the separator for yang schemas.
	YangSchemaCompletionSeperator = string([]byte{' ', '/'})
)

// The idea of path suggestions for a SBI's YANG schema is heavily inspired by
// gnmic (https://github.com/karimra/gnmic)

// YangSchemaCompleter represents the YangSchemaCompleter.
type YangSchemaCompleter struct {
	Cache      map[string]*yang.Entry
	Entry      *yang.Entry
	IgnoreCase bool
}

// NewYangSchemaCompleter returns a new YangSchemaCompleter with the cache
// intialised with the provided yang.Entry.
func NewYangSchemaCompleter(e *yang.Entry, ic bool) *YangSchemaCompleter {
	return &YangSchemaCompleter{
		Cache: map[string]*yang.Entry{
			"": e,
		},
		Entry:      e,
		IgnoreCase: ic,
	}
}

func (c *YangSchemaCompleter) cleanSchemaPath(e *yang.Entry, path string) (*yang.Entry, string) {
	if path == "" {
		return c.Cache[""], ""
	}

	var endsWithSeparator bool
	if len(path) >= 1 && path[len(path)-1] == os.PathSeparator {
		endsWithSeparator = true
	}

	// return last element of a path
	base := filepath.Base(path)
	cleanPath := cleanPathString(path[:len(path)-len(base)])

	if !endsWithSeparator {
		if ci, ok := c.Cache[cleanPath]; ok {
			return ci, base
		}
	} else {
		cleanBasePath := cleanPathString(base)
		if f := e.Find(cleanBasePath); f != nil {
			e = f
			c.Cache[path] = e
		}
		base = ""
	}

	return e, base
}

func cleanPathString(inputPath string) string {
	sampleRegexp := regexp.MustCompile(`\[.*?\]`)
	cleanPath := sampleRegexp.ReplaceAllString(inputPath, "")

	return cleanPath
}

func promptFromYangEntry(e *yang.Entry) []prompt.Suggest {
	if e != nil {
		if e.IsDir() {
			suggestions := make([]prompt.Suggest, 0, len(e.Dir))
			for _, v := range e.Dir {
				permission := "[rw]"
				if v.ReadOnly() {
					permission = "[ro]"
				}
				permDesc := permission + " " + v.Description
				suggestions = append(suggestions, prompt.Suggest{
					Text:        v.Name,
					Description: permDesc,
				})
				if v.Key != "" {
					for _, key := range strings.Fields(v.Key) {
						suggestionWithKey := fmt.Sprintf("%s[%s=*]", v.Name, key)
						suggestions = append(suggestions, prompt.Suggest{
							Text:        suggestionWithKey,
							Description: permDesc,
						})
					}
				}
			}
			return SortSuggestionByText(suggestions)
		}
	}

	return []prompt.Suggest{}
}

func (c *YangSchemaCompleter) UpdateEntry(p string) error {
	base := filepath.Base(p)
	// return last element of a path
	if f := c.Entry.Find(base); f != nil {
		c.Entry = f
		return nil
	}
	return fmt.Errorf("could not find path: %s", base)
}

// Complete provides the actual completion.
func (c *YangSchemaCompleter) Complete(d prompt.Document) []prompt.Suggest {
	p := d.GetWordBeforeCursor()

	entry, base := c.cleanSchemaPath(c.Entry, p)
	c.Entry = entry

	return prompt.FilterHasPrefix(promptFromYangEntry(c.Entry), base, c.IgnoreCase)
}
