package main

import (
	"errors"
	"fmt"
	"strings"

	"github.com/pborman/uuid"
	"golang.org/x/crypto/bcrypt"
)

type User struct {
	Username string   `json:username`
	Uuid     string   `json:uuid`
	SubSlugs []string `json:subs`
	IsAdmin  bool     `json:admin`
}

func NewUser(un string) *User {
	u := new(User)
	u.Username = un
	u.Uuid = uuid.New()
	return u
}

/*
func (u *User) UpdateFeed() error {
	for _, slug := range u.SubSlugs {
		pts := strings.Split(slug, ";")
		if len(pts) != 2 {
			continue
		}
		if comic, err := m.GetComic(pts[0], pts[1]); err == nil {
			comic.Update()
		}
	}
	return nil
}
*/

func (m *model) SaveUser(u *User) error {
	var err error
	if err = m.bolt.OpenDB(); err != nil {
		return err
	}
	defer m.bolt.CloseDB()
	bkt := []string{"users", u.Uuid}
	if err = m.bolt.MkBucketPath(bkt); err != nil {
		return err
	}
	if err = m.bolt.SetValue(bkt, "username", u.Username); err != nil {
		return err
	}
	var adminVal = "false"
	if u.IsAdmin {
		adminVal = "true"
	}
	if err = m.bolt.SetValue(bkt, "admin", adminVal); err != nil {
		return err
	}
	var newSubs []string
	for _, v := range u.SubSlugs {
		if strings.TrimSpace(v) != "" {
			newSubs = append(newSubs, v)
		}
	}
	slugs := strings.Join(newSubs, ",")
	if err = m.bolt.SetValue(bkt, "subs", slugs); err != nil {
		return err
	}
	return nil
}

func (m *model) isValidUser(uid string) bool {
	var err error
	if err = m.bolt.OpenDB(); err != nil {
		return false
	}
	defer m.bolt.CloseDB()
	ret := new(User)
	bkt := []string{"users", uid}
	ret.Uuid = uid
	if ret.Username, err = m.bolt.GetValue(bkt, "username"); err != nil {
		fmt.Println("Error getting username value:", err.Error())
		return false
	}
	return true
}

func (m *model) GetUserByEmail(email string) (*User, error) {
	if email == "" {
		return nil, errors.New("No email given")
	}
	u := m.GetAllUsers()
	for i := range u {
		if u[i].Username == email {
			return &u[i], nil
		}
	}
	return nil, errors.New("No user found")
}

func (m *model) GetUser(uid string) (*User, error) {
	if uid == "" {
		return nil, errors.New("No user id given")
	}
	var err error
	if err = m.bolt.OpenDB(); err != nil {
		return nil, err
	}
	defer m.bolt.CloseDB()
	ret := new(User)
	bkt := []string{"users", uid}
	ret.Uuid = uid
	if ret.Username, err = m.bolt.GetValue(bkt, "username"); err != nil {
		fmt.Println("Error getting username value:", err.Error())
		return nil, err
	}
	admin, _ := m.bolt.GetValue(bkt, "admin")
	ret.IsAdmin = (admin == "true")
	var subs string
	if subs, err = m.bolt.GetValue(bkt, "subs"); err != nil {
		return nil, err
	}
	ret.SubSlugs = strings.Split(subs, ",")
	return ret, nil
}

func (m *model) SaveAllUsers(users []User) {
	var err error
	if err = m.bolt.OpenDB(); err != nil {
		return
	}
	defer m.bolt.CloseDB()
	for i := range users {
		m.SaveUser(&users[i])
	}
}

func (m *model) LoadUsers() error {
	m.Users = m.GetAllUsers()
	return nil
}

func (m *model) GetAllUsers() []User {
	var err error
	var ret []User
	if err = m.bolt.OpenDB(); err != nil {
		return ret
	}
	defer m.bolt.CloseDB()

	uids := m.GetUserIdList()
	for _, uid := range uids {
		if u, e := m.GetUser(uid); e == nil {
			ret = append(ret, *u)
		}
	}
	return ret
}

func (m *model) GetUserByName(nm string) (*User, error) {
	var err error
	if err = m.bolt.OpenDB(); err != nil {
		return nil, err
	}
	defer m.bolt.CloseDB()
	usrids := m.GetUserIdList()
	for i := range usrids {
		bkt := []string{"users", usrids[i]}
		var tstuname string
		if tstuname, _ = m.bolt.GetValue(bkt, "username"); tstuname == nm {
			// Found it
			return m.GetUser(usrids[i])
		}
	}
	return nil, errors.New("No user with username " + nm + " found")
}

func (m *model) GetUserIdList() []string {
	var ret []string
	var err error
	if err = m.bolt.OpenDB(); err != nil {
		return ret
	}
	defer m.bolt.CloseDB()
	bkt := []string{"users"}
	ret, _ = m.bolt.GetBucketList(bkt)
	return ret
}

// updateUserPassword
// Takes a user id and a password
// Fails if the user doesn't exist
func (m *model) updateUserPassword(uid, password string) error {
	cryptPw, cryptError := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if cryptError != nil {
		return cryptError
	}
	if _, err := m.GetUser(uid); err != nil {
		return err
	}

	if err := m.bolt.OpenDB(); err != nil {
		return err
	}
	defer m.bolt.CloseDB()

	usrPath := []string{"users", uid}
	return m.bolt.SetValue(usrPath, "password", string(cryptPw))
}

// Is the uid and pw given valid?
func (m *model) checkCredentials(uid, pw string) error {
	var err error
	if err = m.bolt.OpenDB(); err != nil {
		return err
	}
	defer m.bolt.CloseDB()

	var uPw string
	usrPath := []string{"users", uid}
	if uPw, err = m.bolt.GetValue(usrPath, "password"); err != nil {
		return err
	}
	return bcrypt.CompareHashAndPassword([]byte(uPw), []byte(pw))
}

func (m *model) hasAdminUser() bool {
	users := m.GetAllUsers()
	for i := range users {
		if users[i].IsAdmin {
			return true
		}
	}
	return false
}
