package models

import (
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/labstack/echo"
	"golang.org/x/crypto/bcrypt"
)

// UserLogin Object to recive user credentials in JSON format
type UserLogin struct {
	Username string `json:"username" form:"username"`
	Password string `json:"password" form:"password"`
}

// User holds information about an user
type User struct {
	ID       int64  `xorm:"int(11) autoincr not null unique pk"`
	Name     string `xorm:"varchar(250)"`
	Username string `xorm:"varchar(250) not null unique"`
	Password string `xorm:"varchar(250) not null"`
	Email    string `xorm:"varchar(250)"`
	Created  int64  `xorm:"created"`
	Updated  int64  `xorm:"updated"`
}

// UserLog logs user actions
type UserLog struct {
	ID     int64  `xorm:"int(11) autoincr not null unique pk"`
	UserID int64  `xorm:"int(11)"`
	Log    string `xorm:"varchar(250)"`
	ItemID int64  `xorm:"int(11)"`
	Time   int64  `xorm:"created"`
}

// TableName returns the table name for users
func (User) TableName() string {
	return "users"
}

// GetUserByID gets informations about a user by its ID
func GetUserByID(id int64) (user User, exists bool, err error) {
	return GetUser(User{ID: id})
}

// GetUser gets a user object
func GetUser(user User) (userOut User, exists bool, err error) {
	userOut = user
	exists, err = x.Get(&userOut)
	return userOut, exists, err
}

// CreateUser creates a new user and inserts it into the database
func CreateUser(user User) (newUser User, err error) {

	newUser = user

	// Check if we have all needed informations
	if newUser.Password == "" || newUser.Username == "" {
		return User{}, fmt.Errorf("you need to specify at least a username and a password")
	}

	// Check if the user already existst
	_, exists, err := GetUser(User{Name: newUser.Name})
	if err != nil {
		return User{}, err
	}
	if exists {
		return User{}, fmt.Errorf("this username is already taken. Please use another")
	}

	// Hash the password
	newUser.Password, err = hashPassword(user.Password)
	if err != nil {
		return User{}, err
	}

	// Insert it
	_, err = x.Insert(newUser)
	if err != nil {
		return User{}, err
	}

	return newUser, nil
}

// HashPassword hashes a password
func hashPassword(password string) (string, error) {
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
	return string(bytes), err
}

// CheckUserCredentials checks user credentials
func CheckUserCredentials(u *UserLogin) (User, error) {

	// Check if the user exists
	var user = User{Username: u.Username}
	exists, err := x.Get(&user)
	if err != nil {
		return User{}, err
	}

	if !exists {
		return User{}, fmt.Errorf("user does not exist")
	}

	// Check the users password
	err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(u.Password))

	if err != nil {
		return User{}, err
	}

	return user, nil
}

// GetCurrentUser returns the current user based on its jwt token
func GetCurrentUser(c echo.Context) (user User, err error) {
	jwtinf := c.Get("user").(*jwt.Token)
	claims := jwtinf.Claims.(jwt.MapClaims)
	userID, ok := claims["id"].(float64)
	if !ok {
		return user, fmt.Errorf("Error getting UserID")
	}
	user = User{
		ID:       int64(userID),
		Name:     claims["name"].(string),
		Email:    claims["email"].(string),
		Username: claims["username"].(string),
	}

	return
}

// LogAction logs a user action
func logAction(action string, user User, itemID int64) (err error) {
	_, err = x.Insert(UserLog{Log: action, UserID: user.ID, ItemID: itemID})
	return
}

// LogAction logs a user action
func LogAction(action string, itemID int64, c echo.Context) (err error) {
	// Get the user options
	user, err := GetCurrentUser(c)
	if err != nil {
		return err
	}

	return logAction(action, user, itemID)
}
