package authorization

import (
	"encoding/json"
	"fmt"
	"time"

	"code.justin.tv/amzn/TwitchS2S2/internal/token"
	"code.justin.tv/sse/jwt/claim"
)

// Authorization represents the validated claims on an authorization token.
type Authorization struct {
	Subject  Subject
	Audience Audience
	Scope    token.Scope

	JWTID      string
	Issuer     string
	Active     bool
	NotBefore  time.Time
	IssuedAt   time.Time
	Expiration time.Time
}

// ValidateTimeClaims validates the time claims on this authorization.
func (a *Authorization) ValidateTimeClaims() error {
	now := time.Now().UTC()
	if a.NotBefore.After(now) {
		return &ErrInvalidToken{
			Field:  "nbf",
			Reason: fmt.Sprintf("Token used before nbf<%s> at %s", a.NotBefore.String(), now.String()),
		}
	}
	if now.After(a.Expiration) {
		return &ErrInvalidToken{
			Field:  "exp",
			Reason: fmt.Sprintf("Token used after exp<%s> at %s", a.Expiration.String(), now.String()),
		}
	}
	return nil
}

type jsonMarshalableAuthorization struct {
	JWTID      string    `json:"jti"`
	Issuer     string    `json:"iss"`
	Subject    string    `json:"sub"`
	NotBefore  claim.Nbf `json:"nbf"`
	IssuedAt   claim.Iat `json:"iat"`
	Expiration claim.Exp `json:"exp"`
	Scope      string    `json:"scope"`
	Active     bool      `json:"active"`
}

// MarshalJSON implements json.Marshaler
func (a Authorization) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		jsonMarshalableAuthorization
		Audience Audience `json:"aud"`
	}{
		jsonMarshalableAuthorization: jsonMarshalableAuthorization{
			JWTID:      a.JWTID,
			Issuer:     a.Issuer,
			Subject:    a.Subject.ID(),
			NotBefore:  claim.Nbf(a.NotBefore),
			IssuedAt:   claim.Iat(a.IssuedAt),
			Expiration: claim.Exp(a.Expiration),
			Scope:      a.Scope.String(),
			Active:     a.Active,
		},
		Audience: a.Audience,
	})
}

// UnmarshalJSON implements json.Marshaler
func (a *Authorization) UnmarshalJSON(in []byte) error {
	var claims struct {
		jsonMarshalableAuthorization
		Audience *audience `json:"aud"`
	}
	if err := json.Unmarshal(in, &claims); err != nil {
		return err
	}

	a.Subject = &subject{id: claims.Subject}
	a.Audience = claims.Audience
	a.Scope = token.ParseScope(claims.Scope)

	a.JWTID = claims.JWTID
	a.Issuer = claims.Issuer
	a.Active = claims.Active
	a.NotBefore = time.Time(claims.NotBefore).UTC()
	a.IssuedAt = time.Time(claims.IssuedAt).UTC()
	a.Expiration = time.Time(claims.Expiration).UTC()
	return nil
}
