package guardian

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"code.justin.tv/systems/guardian/osin"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/dynamodb"
)

// enums for token type
const (
	AuthorizationCodeType = "auth"
	RefreshTokenType      = "refresh"
	AccessTokenType       = "access"
)

func authorizeDataToAttributeValues(data *osin.AuthorizeData) (params map[string]*dynamodb.AttributeValue) {
	params = map[string]*dynamodb.AttributeValue{
		"client_id":    {S: aws.String(data.Client.GetID())},
		"token_hash":   {S: aws.String(data.Code)},
		"expires_in":   {N: aws.String(strconv.FormatInt(int64(data.ExpiresIn), 10))},
		"callback_url": {S: aws.String(data.RedirectURI)},
		"state":        {S: aws.String(data.State)},
		"valid_from":   {N: aws.String(strconv.FormatInt(int64(data.CreatedAt.Unix()), 10))},
		"type":         {S: aws.String(AuthorizationCodeType)},
	}
	if user, ok := data.UserData.(*User); ok {
		params["user"] = &dynamodb.AttributeValue{S: aws.String(user.CN)}
	}

	var scopes []string
	if data.Scope != "" {
		scopes = strings.Split(data.Scope, ",")
	} else {
		scopes = DefaultClientScopes
	}
	params["scopes"] = &dynamodb.AttributeValue{SS: aws.StringSlice(scopes)}

	return
}

func attributeValuesToAuthorizeData(params map[string]*dynamodb.AttributeValue, db *Storage) (data *osin.AuthorizeData, err error) {
	// no authorization
	if len(params) == 0 {
		return
	}

	client, err := db.GetClient(aws.StringValue(params["client_id"].S))
	if err != nil {
		return
	}

	expiresIn, err := strconv.ParseInt(aws.StringValue(params["expires_in"].N), 10, 32)
	if err != nil {
		return
	}

	createdAt, err := strconv.ParseInt(aws.StringValue(params["valid_from"].N), 10, 64)
	if err != nil {
		return
	}

	data = &osin.AuthorizeData{
		Client:      client,
		ExpiresIn:   int32(expiresIn),
		Scope:       strings.Join(aws.StringValueSlice(params["scopes"].SS), ","),
		CreatedAt:   time.Unix(createdAt, 0),
		UserData:    &User{CN: aws.StringValue(params["user"].S)},
		RedirectURI: aws.StringValue(params["callback_url"].S),
	}
	return
}

// SaveAuthorize saves authorization to dynamodb
func (db *Storage) SaveAuthorize(data *osin.AuthorizeData) error {
	tokenHash, _, err := DefaultTokenGenerator.HashSecret(data.Code, nil)
	if err != nil {
		return fmt.Errorf("guardian: error hashing authorization code: %s", err.Error())
	}

	item := authorizeDataToAttributeValues(data)
	item["token_hash"] = &dynamodb.AttributeValue{S: aws.String(tokenHash)}

	params := &dynamodb.PutItemInput{
		TableName: aws.String(db.Config.AuthorizationsTable),
		Item:      item,
	}

	_, err = db.DB.PutItem(params)
	if err != nil {
		return fmt.Errorf("Guardian Storage Error: %s", err.Error())
	}

	return nil
}

// LoadAuthorize retrieves authorization data keyed by authorization code
// from dynamodb
func (db *Storage) LoadAuthorize(code string) (data *osin.AuthorizeData, err error) {
	codeHash, _, err := DefaultTokenGenerator.HashSecret(code, nil)
	if err != nil {
		err = fmt.Errorf("guardian: error hashing authorization code: %s", err.Error())
		return
	}

	params := &dynamodb.GetItemInput{
		TableName: aws.String(db.Config.AuthorizationsTable),
		Key: map[string]*dynamodb.AttributeValue{
			"token_hash": {S: aws.String(codeHash)},
		},
		ConsistentRead: aws.Bool(true),
	}
	output, err := db.DB.GetItem(params)
	if err != nil {
		return
	}

	data, err = attributeValuesToAuthorizeData(output.Item, db)
	if err != nil {
		return
	}
	return
}

// RemoveAuthorize removes authorization code from dynamodb
func (db *Storage) RemoveAuthorize(code string) (err error) {
	codeHash, _, err := DefaultTokenGenerator.HashSecret(code, nil)
	if err != nil {
		err = fmt.Errorf("guardian: error hashing authorization code: %s", err.Error())
		return
	}

	params := &dynamodb.DeleteItemInput{
		TableName: aws.String(db.Config.AuthorizationsTable),
		Key: map[string]*dynamodb.AttributeValue{
			"token_hash": {S: aws.String(codeHash)},
		},
	}
	_, err = db.DB.DeleteItem(params)
	if err != nil {
		return
	}
	return
}
