package models

import (
	"errors"
	"os"
	"strings"

	"github.com/tobischo/gokeepasslib"
	kp "github.com/tobischo/gokeepasslib"
)

type KeePassDB struct {
	path string
	db   *gokeepasslib.Database
}

func NewKeePassDB(filename string, auth string) (*KeePassDB, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	ret := KeePassDB{
		path: filename,
		db:   kp.NewDatabase(),
	}
	ret.db.Credentials = kp.NewPasswordCredentials(auth)
	err = kp.NewDecoder(file).Decode(ret.db)
	if err != nil {
		return nil, err
	}
	ret.db.UnlockProtectedEntries()
	return &ret, nil
}

func (db *KeePassDB) GetAllEntriesFromRoot() [][]string {
	var res [][]string
	groups := db.db.Content.Root.Groups
	for _, g := range groups {
		grpRes := db.GetAllEntriesInGroup(g)
		res = append(res, grpRes...)
	}
	return res
}
func (db *KeePassDB) GetAllEntriesInGroup(group kp.Group) [][]string {
	var res [][]string
	for _, g := range group.Groups {
		grpRes := db.GetAllEntriesInGroup(g)
		for _, r := range grpRes {
			res = append(res, append([]string{group.Name}, r...))
		}
	}
	for _, e := range group.Entries {
		res = append(res, []string{group.Name, e.GetTitle()})
	}
	return res
}

func (db *KeePassDB) GetGroupsAndEntriesFromRoot(path []string) [][]string {
	var res [][]string
	groups := db.db.Content.Root.Groups
	if len(path) == 0 {
		// Return all groups/entries at this level
		for _, g := range groups {
			res = append(res, []string{g.Name})
		}
		return res
	} else {
		useCase := strings.ToLower(path[0]) != path[0]
		for _, g := range groups {
			var match bool
			if useCase {
				match = strings.HasPrefix(g.Name, path[0])
			} else {
				match = strings.HasPrefix(strings.ToLower(g.Name), path[0])
			}
			if match {
				grpRes := db.GetGroupsAndEntries(g, path[1:])
				for _, r := range grpRes {
					res = append(res, append([]string{path[0]}, r...))
				}
			}
		}
	}
	return res
}

func (db *KeePassDB) GetGroupsAndEntries(group kp.Group, path []string) [][]string {
	var res [][]string
	if len(path) == 0 {
		// Return all groups/entries at this level
		for _, g := range group.Groups {
			res = append(res, []string{g.Name})
		}
		for _, e := range group.Entries {
			res = append(res, []string{e.GetTitle()})
		}
	} else {
		useCase := strings.ToLower(path[0]) != path[0]
		for _, g := range group.Groups {
			var match bool
			if useCase {
				match = strings.HasPrefix(g.Name, path[0])
			} else {
				match = strings.HasPrefix(strings.ToLower(g.Name), path[0])
			}
			if match {
				grpRes := db.GetGroupsAndEntries(g, path[1:])
				for _, r := range grpRes {
					res = append(res, append([]string{path[0]}, r...))
				}
			}
		}
		for _, e := range group.Entries {
			var match bool
			if useCase {
				match = strings.HasPrefix(e.GetTitle(), path[0])
			} else {
				match = strings.HasPrefix(strings.ToLower(e.GetTitle()), path[0])
			}
			if match {
				res = append(res, []string{e.GetTitle()})
			}
		}
	}
	return res
}

func (db *KeePassDB) GetEntryPassword(path []string) (string, error) {
	entry, err := db.FindEntryFromRoot(path)
	if err != nil {
		return "", err
	}
	return entry.GetPassword(), nil
}

func (db *KeePassDB) FindEntryFromRoot(path []string) (*kp.Entry, error) {
	if len(path) < 2 {
		return nil, errors.New("Invalid Path")
	}
	groups := db.db.Content.Root.Groups
	for _, g := range groups {
		if g.Name == path[0] {
			return db.FindEntryInGroup(g, path[1:])
		}
	}
	return nil, errors.New("Invalid Path")
}

func (db *KeePassDB) FindEntryInGroup(group kp.Group, path []string) (*kp.Entry, error) {
	if len(path) == 0 {
		return nil, errors.New("Invalid Path")
	} else if len(path) == 1 {
		// Looking for an entry in this group
		for _, e := range group.Entries {
			if e.GetTitle() == path[0] {
				return &e, nil
			}
		}
		return nil, errors.New("Invalid Path")
	}
	for _, g := range group.Groups {
		if g.Name == path[0] {
			return db.FindEntryInGroup(g, path[1:])
		}
	}
	return nil, errors.New("Invalid Path")
}
