package goauthorization

import (
	"crypto/rsa"
	"fmt"

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

var (
	publicKey *rsa.PublicKey
	audience  string
)

// Decoder struct helps target services mock decoder functions
type Decoder interface {
	ValidateTokens(CapabilityClaims) bool
	Validate(CapabilityClaims) error
	Parse(string) error
}

// InitializeDecoder initializes necessary constants publicKey, audience, and issuer
func InitializeDecoder(publicKeyPath, aud string, iss string) error {
	key, err := jwt.ReadRSAPublicKey(publicKeyPath)
	if err != nil {
		return err
	}
	publicKey = key
	audience = aud
	issuer = iss

	return nil
}

// ValidateTokens takes an array of auth tokens and returns an error if none of them are valid
func (t *AuthorizationTokens) ValidateTokens(capabilities CapabilityClaims) (err error) {
	for _, val := range *t {
		if err = val.Validate(capabilities); err == nil {
			return nil
		}
	}
	return err
}

// Validate is the equivalent of ValidateTokens but on a single token
func (t *AuthorizationToken) Validate(capabilities CapabilityClaims) (err error) {
	validatedClaims := t.validateClaims()
	validatedCaps := t.validateCapabilities(capabilities)

	if err = validatedClaims; err != nil {
		return err
	}
	if err = validatedCaps; err != nil {
		return err
	}

	return err
}

// Parse uses jwt library to parse a serialized jwt string into an AuthorizationToken
func (t *AuthorizationToken) Parse(serialized string) (err error) {
	err = jwt.DecodeAndValidate(&t.Header, &t.Claims, t.Algorithm, []byte(serialized))
	if err != nil {
		return err
	}

	return
}

func (t *AuthorizationToken) validateCapabilities(capabilities CapabilityClaims) error {
	for capName, capArgs := range capabilities {
		tokenAuthorizations := t.Claims.Authorizations[capName]
		if tokenAuthorizations == nil {
			return fmt.Errorf("Capability %v not in token", capName)
		}
		for arg, argv := range capArgs {
			tokenArg := tokenAuthorizations[arg]

			var compare interface{}
			switch tokenArg.(type) {
			case float64:
				compare = int(tokenArg.(float64))
			default:
				compare = tokenArg
			}

			if compare != argv {
				return fmt.Errorf("Invalid cap: Expected: %v Got: %v", argv, tokenAuthorizations[arg])
			}
		}
	}

	return nil
}

func (t *AuthorizationToken) validateClaims() error {
	if !t.validateAudience(audience) {
		return fmt.Errorf("Invalid audience: Expected: %v Got: %v", audience, t.Claims.Audience)
	}
	if string(t.Claims.Issuer) != issuer {
		return fmt.Errorf("Invalid issuer: Expected: %v, Got: %v", issuer, t.Claims.Issuer)
	}

	return nil
}

func (t *AuthorizationToken) validateAudience(audience string) bool {
	for _, v := range t.Claims.Audience {
		if v == audience {
			return true
		}
	}
	return false
}
