package auth

import (
	"fmt"
	"time"

	"github.com/dgrijalva/jwt-go"
)

// Token constants
const (
	TokenIssuer           = "amplifier"
	TokenTypeVerification = "verification"
	TokenTypeLogin        = "login"
	TokenTypePassword     = "password"

	VerificationTokenValidFor = time.Hour
	LoginTokenValidFor        = 24 * time.Hour
	PasswordTokenValidFor     = time.Hour
)

// AuthClaims represents authentication claims
type AuthClaims struct {
	Type               string `json:"Type"`
	AccountName        string `json:"AccountName"`
	ActiveOrganization string `json:"ActiveOrganization"`
	jwt.StandardClaims
}

type Tokens struct {
	secretKey []byte
}

func New(secretKey string) *Tokens {
	return &Tokens{secretKey: []byte(secretKey)}
}

// CreateVerificationToken creates a verification token for a given user
func (t *Tokens) CreateVerificationToken(name string) (string, error) {
	claims := AuthClaims{
		Type:        TokenTypeVerification,
		AccountName: name,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(VerificationTokenValidFor).Unix(),
			Issuer:    TokenIssuer,
		},
	}
	return t.createToken(claims)
}

// CreateLoginToken creates a login token for a given account
func (t *Tokens) CreateLoginToken(name string, activeOrganization string) (string, error) {
	claims := AuthClaims{
		Type:               TokenTypeLogin,
		AccountName:        name,
		ActiveOrganization: activeOrganization,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(LoginTokenValidFor).Unix(),
			Issuer:    TokenIssuer,
		},
	}
	return t.createToken(claims)
}

// CreatePasswordToken creates a password token for a given user name
func (t *Tokens) CreatePasswordToken(name string) (string, error) {
	claims := AuthClaims{
		Type:        TokenTypePassword,
		AccountName: name,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(PasswordTokenValidFor).Unix(),
			Issuer:    TokenIssuer,
		},
	}
	return t.createToken(claims)
}

// createToken creates a token for a given user name
func (t *Tokens) createToken(claims jwt.Claims) (string, error) {
	// Forge the token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	ss, err := token.SignedString(t.secretKey)
	if err != nil {
		return "", fmt.Errorf("unable to issue token")
	}
	return ss, nil
}

// ValidateToken validates a token and return its claims
func (t *Tokens) ValidateToken(signedString string, tokenType string) (*AuthClaims, error) {
	token, err := jwt.ParseWithClaims(signedString, &AuthClaims{}, func(token *jwt.Token) (interface{}, error) {
		return t.secretKey, nil
	})
	if err != nil {
		return nil, err
	}
	if !token.Valid {
		return nil, fmt.Errorf("invalid token")
	}
	claims, ok := token.Claims.(*AuthClaims)
	if !ok {
		return nil, fmt.Errorf("invalid claims")
	}
	if claims.Type != tokenType {
		return nil, fmt.Errorf("invalid token type")
	}
	return claims, nil
}
