package auth

import (
	"errors"
	"github.com/openaccounting/oa-server/core/model/db"
	"github.com/openaccounting/oa-server/core/model/types"
	"github.com/openaccounting/oa-server/core/util"
)

var Instance Interface

type AuthService struct {
	db     db.Datastore
	bcrypt util.Bcrypt
}

// AuthRepository interface for dependency injection
type AuthRepository interface {
	GetVerifiedUserByEmail(string) (*types.User, error)
	GetUserByActiveSession(string) (*types.User, error)
	GetUserByApiKey(string) (*types.User, error)
	GetUserByEmailVerifyCode(string) (*types.User, error)
	UpdateSessionActivity(string) error
	UpdateApiKeyActivity(string) error
}

// GormAuthService uses the repository pattern
type GormAuthService struct {
	repository AuthRepository
	bcrypt     util.Bcrypt
}

type Interface interface {
	Authenticate(string, string) (*types.User, error)
	AuthenticateUser(email string, password string) (*types.User, error)
	AuthenticateSession(string) (*types.User, error)
	AuthenticateApiKey(string) (*types.User, error)
	AuthenticateEmailVerifyCode(string) (*types.User, error)
}

func NewAuthService(db db.Datastore, bcrypt util.Bcrypt) *AuthService {
	authService := &AuthService{db: db, bcrypt: bcrypt}
	Instance = authService
	return authService
}

func NewGormAuthService(repository AuthRepository, bcrypt util.Bcrypt) *GormAuthService {
	authService := &GormAuthService{repository: repository, bcrypt: bcrypt}
	Instance = authService
	return authService
}

func (auth *AuthService) Authenticate(emailOrKey string, password string) (*types.User, error) {
	// authenticate via session, apikey or user
	user, err := auth.AuthenticateSession(emailOrKey)

	if err == nil {
		return user, nil
	}

	user, err = auth.AuthenticateApiKey(emailOrKey)

	if err == nil {
		return user, nil
	}

	user, err = auth.AuthenticateUser(emailOrKey, password)

	if err == nil {
		return user, nil
	}

	user, err = auth.AuthenticateEmailVerifyCode(emailOrKey)

	if err == nil {
		return user, nil
	}

	return nil, errors.New("Unauthorized")
}

func (auth *AuthService) AuthenticateUser(email string, password string) (*types.User, error) {
	u, err := auth.db.GetVerifiedUserByEmail(email)

	if err != nil {
		return nil, errors.New("Invalid email or password")
	}

	err = auth.bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password))

	if err != nil {
		return nil, errors.New("Invalid email or password")
	}

	return u, nil
}

func (auth *AuthService) AuthenticateSession(id string) (*types.User, error) {
	u, err := auth.db.GetUserByActiveSession(id)

	if err != nil {
		return nil, errors.New("Invalid session")
	}

	auth.db.UpdateSessionActivity(id)

	return u, nil
}

func (auth *AuthService) AuthenticateApiKey(id string) (*types.User, error) {
	u, err := auth.db.GetUserByApiKey(id)

	if err != nil {
		return nil, errors.New("Access denied")
	}

	auth.db.UpdateApiKeyActivity(id)

	return u, nil
}

func (auth *AuthService) AuthenticateEmailVerifyCode(code string) (*types.User, error) {
	u, err := auth.db.GetUserByEmailVerifyCode(code)

	if err != nil {
		return nil, errors.New("Access denied")
	}

	return u, nil
}

// GormAuthService implementations
func (auth *GormAuthService) Authenticate(emailOrKey string, password string) (*types.User, error) {
	// authenticate via session, apikey or user
	user, err := auth.AuthenticateSession(emailOrKey)

	if err == nil {
		return user, nil
	}

	user, err = auth.AuthenticateApiKey(emailOrKey)

	if err == nil {
		return user, nil
	}

	user, err = auth.AuthenticateUser(emailOrKey, password)

	if err == nil {
		return user, nil
	}

	user, err = auth.AuthenticateEmailVerifyCode(emailOrKey)

	if err == nil {
		return user, nil
	}

	return nil, errors.New("Unauthorized")
}

func (auth *GormAuthService) AuthenticateUser(email string, password string) (*types.User, error) {
	u, err := auth.repository.GetVerifiedUserByEmail(email)

	if err != nil {
		return nil, errors.New("Invalid email or password")
	}

	err = auth.bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password))

	if err != nil {
		return nil, errors.New("Invalid email or password")
	}

	return u, nil
}

func (auth *GormAuthService) AuthenticateSession(id string) (*types.User, error) {
	u, err := auth.repository.GetUserByActiveSession(id)

	if err != nil {
		return nil, errors.New("Invalid session")
	}

	auth.repository.UpdateSessionActivity(id)

	return u, nil
}

func (auth *GormAuthService) AuthenticateApiKey(id string) (*types.User, error) {
	u, err := auth.repository.GetUserByApiKey(id)

	if err != nil {
		return nil, errors.New("Access denied")
	}

	auth.repository.UpdateApiKeyActivity(id)

	return u, nil
}

func (auth *GormAuthService) AuthenticateEmailVerifyCode(code string) (*types.User, error) {
	u, err := auth.repository.GetUserByEmailVerifyCode(code)

	if err != nil {
		return nil, errors.New("Access denied")
	}

	return u, nil
}
