package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/go-martini/martini"

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

const (
	//DefaultContextKey jwt
	DefaultContextKey = "jwt"
)

// errorHandler error callback
type errorHandler func(http.ResponseWriter, string)

// TokenExtractor TokenExtractor
type TokenExtractor func(*http.Request) (string, error)

// Config config
type Config struct {
	ValidationKeyGetter jwt.Keyfunc

	ContextKey string

	ErrorHandler errorHandler

	CredentialsOptional bool

	Extractor TokenExtractor
	// Default: true
	Debug bool

	// Default: false
	EnableAuthOnOptions bool

	// Default: nil
	SigningMethod jwt.SigningMethod

	// Default: false
	Expiration bool
}

// Middleware the middleware for JSON Web tokens authentication method
type Middleware struct {
	Config Config
}

// OnError default error handler
func OnError(res http.ResponseWriter, err string) {
	http.Error(res, err, http.StatusUnauthorized)
}

// New constructs a new Secure instance with supplied options.
func New(cfg ...Config) *Middleware {

	var c Config
	if len(cfg) == 0 {
		c = Config{}
	} else {
		c = cfg[0]
	}

	if c.ContextKey == "" {
		c.ContextKey = DefaultContextKey
	}

	if c.ErrorHandler == nil {
		c.ErrorHandler = OnError
	}

	if c.Extractor == nil {
		c.Extractor = FromAuthHeader
	}

	return &Middleware{Config: c}
}

func (m *Middleware) logf(format string, args ...interface{}) {
	if m.Config.Debug {
		log.Printf(format, args...)
	}
}

// Get returns the user (&token) information for this client/request
// func (m *Middleware) Get(req *http.Request) *jwt.Token {
// 	return req.Header.Get(m.Config.ContextKey).(*jwt.Token)
// }

// Serve the middleware's action
func (m *Middleware) Serve(ctx martini.Context, res http.ResponseWriter, req *http.Request) {
	if err := m.CheckJWT(req, res, ctx); err != nil {
		return
	}
	// If everything ok then call next.
	ctx.Next()
}

// FromAuthHeader is a "TokenExtractor" that takes a give context and extracts
// the JWT token from the Authorization header.
func FromAuthHeader(req *http.Request) (string, error) {
	authHeader := req.Header.Get("Authorization")
	if authHeader == "" {
		return "", nil // No error, just no token
	}

	// TODO: Make this a bit more robust, parsing-wise
	authHeaderParts := strings.Split(authHeader, " ")
	if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
		return "", fmt.Errorf("Authorization header format must be Bearer {token}")
	}

	return authHeaderParts[1], nil
}

// CheckJWT the main functionality, checks for token
func (m *Middleware) CheckJWT(req *http.Request, res http.ResponseWriter, ctx martini.Context) error {
	if !m.Config.EnableAuthOnOptions {
		if req.Method == http.MethodOptions {
			return nil
		}
	}

	// Use the specified token extractor to extract a token from the request
	token, err := m.Config.Extractor(req)
	// If debugging is turned on, log the outcome
	if err != nil {
		m.logf("Error extracting JWT: %v", err)
	} else {
		m.logf("Token extracted: %s", token)
	}

	// If an error occurs, call the error handler and return an error
	if err != nil {
		m.Config.ErrorHandler(res, err.Error())
		return fmt.Errorf("Error extracting token: %v", err)
	}

	// If the token is empty...
	if token == "" {
		// Check if it was required
		if m.Config.CredentialsOptional {
			m.logf("  No credentials found (CredentialsOptional=true)")
			// No error, just no token (and that is ok given that CredentialsOptional is true)
			return nil
		}

		// If we get here, the required token is missing
		errorMsg := "Required authorization token not found"
		m.Config.ErrorHandler(res, errorMsg)
		m.logf("  Error: No credentials found (CredentialsOptional=false)")
		return fmt.Errorf(errorMsg)
	}

	// Now parse the token

	parsedToken, err := jwt.Parse(token, m.Config.ValidationKeyGetter)
	// Check if there was an error in parsing...
	if err != nil {
		m.logf("Error parsing token: %v", err)
		m.Config.ErrorHandler(res, err.Error())
		return fmt.Errorf("Error parsing token: %v", err)
	}

	if m.Config.SigningMethod != nil && m.Config.SigningMethod.Alg() != parsedToken.Header["alg"] {
		message := fmt.Sprintf("Expected %s signing method but token specified %s",
			m.Config.SigningMethod.Alg(),
			parsedToken.Header["alg"])
		m.logf("Error validating token algorithm: %s", message)
		m.Config.ErrorHandler(res, errors.New(message).Error())
		return fmt.Errorf("Error validating token algorithm: %s", message)
	}

	// Check if the parsed token is valid...
	if !parsedToken.Valid {
		m.logf("Token is invalid")
		m.Config.ErrorHandler(res, "The token isn't valid")
		return fmt.Errorf("Token is invalid")
	}

	if m.Config.Expiration {
		if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok {
			if expired := claims.VerifyExpiresAt(time.Now().Unix(), true); !expired {
				return fmt.Errorf("Token is expired")
			}
		}
	}

	m.logf("JWT: %v", parsedToken)

	// If we get here, everything worked and we can set the
	// user property in context.
	//ctx.Map(parseToken(parsedToken))
	return nil
}

// 把token解析成一个usertoken对象
// func parseToken(token *jwt.Token) *UserToken {
// 	claims := token.Claims.(jwt.MapClaims)
// 	ut := &UserToken{
// 		UserID:   uint(claims["UserID"].(float64)),
// 		UserName: claims["UserName"].(string),
// 		UserType: claims["UserType"].(string),
// 		RoleCode: int(claims["RoleCode"].(float64)),
// 		VendorID: uint(claims["VendorID"].(float64)),
// 	}
// 	return ut
// }
