package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/dgrijalva/jwt-go"
	"golang.org/x/crypto/bcrypt"
)

var (
	ErrUserNotFound = errors.New("user not found")
)

func init() {
	DB.AddTableWithName(User{}, "users").SetKeys(true, "Id")
}

// A User is a person that has administrative access to bactdb.
// Todo: add password
type User struct {
	Id        int64     `json:"id,omitempty"`
	Username  string    `db:"username" json:"username"`
	Password  string    `db:"password" json:"-"`
	CreatedAt time.Time `db:"created_at" json:"createdAt"`
	UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
	DeletedAt NullTime  `db:"deleted_at" json:"deletedAt"`
}

type UserJSON struct {
	User *User `json:"user"`
}

type UsersJSON struct {
	Users []*User `json:"users"`
}

func (m *User) String() string {
	return fmt.Sprintf("%v", *m)
}

type UserSession struct {
	*User
	Role  string `json:"access_level"`
	Genus string `json:"genus"`
}

func serveAuthenticateUser(w http.ResponseWriter, r *http.Request) {
	var a struct {
		Username string
		Password string
	}
	if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	user_session, err := dbAuthenticate(a.Username, a.Password)
	if err != nil {
		w.Header().Set("Content-Type", "application/json; charset=UTF-8")
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte(`{"error":"Invalid username or password"}`))
		return
	}

	currentTime := time.Now()

	t := jwt.New(jwt.SigningMethodRS256)
	t.Claims["name"] = user_session.Username
	t.Claims["iss"] = "bactdb"
	t.Claims["sub"] = "user@example.com" // TODO: fix this
	t.Claims["role"] = user_session.Role
	t.Claims["genus"] = user_session.Genus
	t.Claims["iat"] = currentTime.Unix()
	t.Claims["exp"] = currentTime.Add(time.Minute * 60 * 24).Unix()
	tokenString, err := t.SignedString(signKey)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	var token struct {
		Token string `json:"token"`
	}
	token.Token = tokenString
	data, err := json.Marshal(token)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.Write(data)
}

func dbAuthenticate(username string, password string) (*UserSession, error) {
	var users []User
	var user_session UserSession

	if err := DBH.Select(&users, `SELECT * FROM users WHERE username=$1;`, username); err != nil {
		return nil, err
	}
	if len(users) == 0 {
		return nil, ErrUserNotFound
	}
	if err := bcrypt.CompareHashAndPassword([]byte(users[0].Password), []byte(password)); err != nil {
		return nil, err
	}
	user_session.User = &users[0]
	user_session.Role = "admin"
	user_session.Genus = "hymenobacter"
	return &user_session, nil
}