package goauthorization

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"

	"code.justin.tv/tshadwell/jwt"
)

// Parser struct helps target services mock parser functions
type Parser interface {
	ParseTokens(*http.Request) (*AuthorizationTokens, error)
	ParseToken(*http.Request) (*AuthorizationToken, error)
	ExtractCapabilities(*http.Request, string, map[string]interface{}) (CapabilityClaims, error)
}

// ParseTokens extracts all jwt tokens from an http request and unmarshals them into an AuthorizationToken
func ParseTokens(r *http.Request) (tokens *AuthorizationTokens, err error) {
	jwtArr := strings.Fields(r.Header.Get("Twitch-Authorization"))
	if len(jwtArr) == 0 {
		return nil, fmt.Errorf("Request contains no JWTs")
	}

	jwts := jwtArr[len(jwtArr)-1]

	authTokens := AuthorizationTokens{}
	for _, val := range strings.Split(jwts, ":") {
		authToken := AuthorizationToken{Algorithm: jwt.RSAValidateOnly(jwt.RS256, publicKey)}
		authToken.Header = jwt.NewHeader(authToken.Algorithm)
		if err = authToken.Parse(val); err != nil {
			return nil, err
		}
		authTokens = append(authTokens, authToken)
	}

	return &authTokens, nil
}

// ParseToken does the same as ParseTokens, but with a single token
func ParseToken(r *http.Request) (token *AuthorizationToken, err error) {
	authToken := AuthorizationToken{Algorithm: jwt.RSAValidateOnly(jwt.RS256, publicKey)}
	authToken.Header = jwt.NewHeader(authToken.Algorithm)
	if err = authToken.Parse(r.Header.Get("Twitch-Authorization")); err != nil {
		return nil, err
	}

	return &authToken, nil
}

// ExtractCapabilities is a helper function takes an http request and expected capability params,
// extracts those values from url params and request body, checks their types against the example parmas,
// and formats them into a CapabilityClaims struct.
// example param hash: params = map[string]interface{}{"user_id": 1, "login": "hassan",}
func ExtractCapabilities(r *http.Request, name string, params map[string]interface{}) (capabilities CapabilityClaims, err error) {
	args := make(map[string]CapValue)
	data := make(map[string]interface{})
	if r.Body != nil {
		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return CapabilityClaims{}, fmt.Errorf("invalid request body")
		}
		json.Unmarshal(b, &data)
	}
	for param := range params {
		urlParam := r.URL.Query().Get(param)
		if urlParam != "" {
			args[param] = urlParam
		} else if data[param] != nil {
			args[param] = data[param]
		} else {
			return CapabilityClaims{}, fmt.Errorf("Param %s could not be found", param)
		}
	}
	capabilities = CapabilityClaims{}
	if len(args) > 0 {
		capabilities[name] = args
	}

	for key, val := range params {
		s := fmt.Sprintf("%v", args[key])
		switch val.(type) {
		case int:
			argVal, err := strconv.Atoi(s)
			if err != nil {
				return CapabilityClaims{}, fmt.Errorf("Param %s (%s) has value of invalid type. Expected int", key, args[key])
			}
			args[key] = argVal
		case bool:
			argVal, err := strconv.ParseBool(s)
			if err != nil {
				return CapabilityClaims{}, fmt.Errorf("Param %s (%s) has value of invalid type. Expected bool", key, args[key])
			}
			args[key] = argVal

		case float64:
			argVal, err := strconv.ParseFloat(s, 64)
			if err != nil {
				return CapabilityClaims{}, fmt.Errorf("Param %s (%s) has value of invalid type. Expected float", key, args[key])
			}
			args[key] = argVal

		case []interface{}:
			args[key] = val

		case string:
			args[key] = s
		default:
			return
		}
	}

	return capabilities, nil

}

// GetTokenClaims is a helper function that extracts and returns the capabilities from an AuthorizationToken
func (t *AuthorizationToken) GetTokenClaims(capName string) (CapabilityClaim, error) {
	var capClaim CapabilityClaim
	caps := t.Claims.Authorizations
	for key := range caps {
		if key == capName {
			capClaim = caps[key]
			return capClaim, nil
		}

	}

	return capClaim, fmt.Errorf("Capability %s does not exist in this token", capName)
}
