package goauthorization

import (
	"crypto/rsa"
	"fmt"
	"time"

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

var (
	privateKey *rsa.PrivateKey
	issuer     string
)

// Encoder struct helps target services mock encoder functions
type Encoder interface {
	Serialize() ([]byte, error)
	String() string
}

// AuthorizationTokens is an array of AuthorizationTokens
type AuthorizationTokens []AuthorizationToken

// AuthorizationToken contains all relevant information stored in a jwt token
type AuthorizationToken struct {
	Header    jwt.Header
	Algorithm jwt.Algorithm
	Claims    TokenClaims
}

// TokenClaims contains all standard jwt claims
type TokenClaims struct {
	Audience       []string         `json:"aud"`
	Expires        claim.Exp        `json:"exp"`
	Issuer         claim.Iss        `json:"iss"`
	NotBefore      claim.Nbf        `json:"nbf"`
	Authorizations CapabilityClaims `json:"capabilities"`
	claim.Sub
}

// CapabilityClaims is a hash of CapabilityClaim
type CapabilityClaims map[string]CapabilityClaim

// CapValue is the value of any capability key, can be of any type
type CapValue interface{}

// CapabilityClaim is a hash of claim key to values
type CapabilityClaim map[string]CapValue

// InitializeEncoder initializes necessary constants privateKey and issuer
func InitializeEncoder(privateKeyPath, iss string) error {
	key, err := jwt.ReadRSAPrivateKey(privateKeyPath)
	if err != nil {
		return err
	}
	privateKey = key
	issuer = iss

	return nil
}

// GenerateToken is a helper function that generates an AuthorizationToken based on input
func GenerateToken(exp, nbf time.Time, aud []string, sub string, authorizations CapabilityClaims) AuthorizationToken {
	tokenClaims := TokenClaims{
		Audience:       aud,
		Expires:        claim.Exp(exp),
		Issuer:         claim.Iss(issuer),
		NotBefore:      claim.Nbf(nbf),
		Sub:            claim.Sub{Sub: sub},
		Authorizations: authorizations}

	token := AuthorizationToken{Algorithm: jwt.RS256(privateKey), Claims: tokenClaims}
	token.Header = jwt.NewHeader(token.Algorithm)

	return token
}

// Serialize encodes an auth token into a byte array
func (t *AuthorizationToken) Serialize() ([]byte, error) {
	return jwt.Encode(t.Header, t.Claims, t.Algorithm)
}

// String encodes an auth token into a string
func (t *AuthorizationToken) String() string {
	encoded, err := jwt.Encode(t.Header, t.Claims, t.Algorithm)
	if err != nil {
		return fmt.Sprintf("Failed to serialize token: %s", err)
	}

	return string(encoded)
}
