package admin

import (
	"gitlab.thpeetz.de/kontor/kontor-go/pkg/dao"
	"log"

	"github.com/gin-gonic/gin"

	"golang.org/x/crypto/bcrypt"
	mgo "gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

// UserDAO extends the type BaseDAO.
type UserDAO struct {
	Db dao.BaseDAO
}

const (
	// USERCOLLECTION defines the collection name for storing application user data.
	USERCOLLECTION = "user"
	// USERMODEL defines the name of the user data model.
	USERMODEL = "kontor.admin.user"
)

// HashPassword returns the encrypted password from password string.
func HashPassword(password string) (string, error) {
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
	return string(bytes), err
}

// CheckPasswordHash returns if password correlates with pasword hash.
func CheckPasswordHash(password, hash string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	return err == nil
}

// FindAll retrieves the list of users from the database.
func (m *UserDAO) FindAll() ([]User, error) {
	m.Db.Connect()
	var users []User
	err := m.Db.MongoDb.C(USERCOLLECTION).Find(bson.M{"model": USERMODEL}).All(&users)
	return users, err
}

// FindByID returns a user with given id or returns the error.
func (m *UserDAO) FindByID(id string) (User, error) {
	m.Db.Connect()
	var user User
	err := m.Db.MongoDb.C(USERCOLLECTION).FindId(bson.ObjectIdHex(id)).One(&user)
	return user, err
}

// FindByUsername returns a user with given name or returns the error.
func (m *UserDAO) FindByUsername(username string) (User, error) {
	m.Db.Connect()
	var user User
	err := m.Db.MongoDb.C(USERCOLLECTION).Find(bson.M{"username": username, "model": USERMODEL}).One(&user)
	return user, err
}

// Insert a user into database.
func (m *UserDAO) Insert(user User) error {
	m.Db.Connect()
	user.Model = USERMODEL
	err := m.Db.MongoDb.C(USERCOLLECTION).Insert(&user)
	return err
}

// Upsert a user into database.
func (m *UserDAO) Upsert(user User) (*mgo.ChangeInfo, error) {
	m.Db.Connect()
	user.Model = USERMODEL
	info, err := m.Db.MongoDb.C(USERCOLLECTION).Upsert(bson.M{"username": user.Username}, &user)
	return info, err
}

// Update an existing user.
func (m *UserDAO) Update(user User) error {
	m.Db.Connect()
	err := m.Db.MongoDb.C(USERCOLLECTION).UpdateId(user.ID, &user)
	return err
}

// Delete an existing user.
func (m *UserDAO) Delete(user User) error {
	m.Db.Connect()
	err := m.Db.MongoDb.C(USERCOLLECTION).Remove(&user)
	return err
}

// IsUserValid checks if the username and password combination is valid
func (m *UserDAO) IsUserValid(username, password string) bool {
	if gin.IsDebugging() {
		log.Printf("UserDAO.IsUserValid(%s)", username)
	}
	user, err := m.FindByUsername(username)
	if gin.IsDebugging() {
		log.Printf("UserDAO.IsUserValid: %v, %v", user, err)
	}
	if &user == nil || err != nil {
		return false
	}
	return CheckPasswordHash(password, user.Password)
}

// IsUserAdmin checks if user identified by name has admin rights.
func (m *UserDAO) IsUserAdmin(username string) bool {
	user, err := m.FindByUsername(username)
	if &user == nil || err != nil {
		return false
	}
	return user.IsAdmin
}

// IsUsernameAvailable checks if the supplied username is available.
func (m *UserDAO) IsUsernameAvailable(username string) bool {
	user, err := m.FindByUsername(username)
	if &user == nil || err != nil {
		return true
	}
	return false
}
