package legacyperm

import (
	"context"
	"fmt"
	"sort"

	"code.justin.tv/beefcake/server/internal/awsutil"
	"code.justin.tv/beefcake/server/internal/config"
	"code.justin.tv/beefcake/server/rpc/beefcake"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
	"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface"
	"github.com/aws/aws-sdk-go/service/dynamodb/expression"
)

// dynamodb attributes
const (
	NameAttribute        = "Name"
	DescriptionAttribute = "Description"
	RolesAttribute       = "Roles"
)

// ErrLegacyPermissionDoesNotExist when item does not exist
type ErrLegacyPermissionDoesNotExist struct {
	ID string
}

func (err ErrLegacyPermissionDoesNotExist) Error() string {
	return fmt.Sprintf("legacy permission does not exist: %s", err.ID)
}

// LegacyPermissions ...
type LegacyPermissions struct {
	Config   *config.Config
	DynamoDB dynamodbiface.DynamoDBAPI
}

// All legacy permissions
func (lp *LegacyPermissions) All(ctx context.Context) ([]*beefcake.GetLegacyPermissionsResponse_LegacyPermission, error) {
	expr, err := expression.NewBuilder().
		WithProjection(expression.NamesList(
			expression.Name("ID"),
			expression.Name("Name"),
			expression.Name("Description"),
		)).
		Build()
	if err != nil {
		return nil, err
	}

	var innerErr error
	all := make([]*beefcake.GetLegacyPermissionsResponse_LegacyPermission, 0, 100)
	if err := lp.DynamoDB.ScanPagesWithContext(ctx,
		&dynamodb.ScanInput{
			ExpressionAttributeNames:  expr.Names(),
			ExpressionAttributeValues: expr.Values(),
			Limit:                     aws.Int64(100),
			ProjectionExpression:      expr.Projection(),
			TableName:                 aws.String(lp.Config.LegacyPermissionsTableName.Get()),
		},
		func(out *dynamodb.ScanOutput, last bool) (cont bool) {
			for _, item := range out.Items {
				var p LegacyPermission
				if innerErr = dynamodbattribute.UnmarshalMap(item, &p); innerErr != nil {
					return false
				}
				all = append(all, &beefcake.GetLegacyPermissionsResponse_LegacyPermission{
					Id:          p.ID,
					Name:        p.Name,
					Description: p.Description,
				})
			}
			return !last
		}); err != nil {
		return nil, err
	}

	if innerErr != nil {
		return nil, innerErr
	}

	sort.Sort(sortableLegacyPermissions(all))

	return all, nil
}

// Create a legacy permission
func (lp *LegacyPermissions) Create(ctx context.Context, id, name, description string) error {
	p := &LegacyPermission{ID: id, Name: name, Description: description}

	item, err := dynamodbattribute.MarshalMap(p)
	if err != nil {
		return err
	}

	expr, err := expression.NewBuilder().
		WithCondition(expression.Name(lp.Config.LegacyPermissionsHashKey.Get()).AttributeNotExists()).
		Build()
	if err != nil {
		return err
	}

	_, err = lp.DynamoDB.PutItemWithContext(ctx, &dynamodb.PutItemInput{
		ConditionExpression:       expr.Condition(),
		ExpressionAttributeNames:  expr.Names(),
		ExpressionAttributeValues: expr.Values(),
		Item:                      item,
		TableName:                 aws.String(lp.Config.LegacyPermissionsTableName.Get()),
	})

	return err
}

// Get a legacy permission
func (lp *LegacyPermissions) Get(ctx context.Context, id string) (*LegacyPermission, error) {
	res, err := lp.DynamoDB.GetItemWithContext(ctx, &dynamodb.GetItemInput{
		Key: map[string]*dynamodb.AttributeValue{
			lp.Config.LegacyPermissionsHashKey.Get(): {S: aws.String(id)},
		},
		TableName: aws.String(lp.Config.LegacyPermissionsTableName.Get()),
	})

	if err != nil {
		if awsutil.IsStatusCode(err, dynamodb.ErrCodeConditionalCheckFailedException) {
			return nil, ErrLegacyPermissionDoesNotExist{ID: id}
		}

		return nil, err
	}

	var out LegacyPermission
	if err := dynamodbattribute.UnmarshalMap(res.Item, &out); err != nil {
		return nil, err
	}

	return &out, err
}

// UpdateOptions ...
type UpdateOptions struct {
	Name        *string
	Description *string
}

// Update a legacy permission
func (lp *LegacyPermissions) Update(ctx context.Context, id string, options UpdateOptions) error {
	if options.Name == nil && options.Description == nil {
		return nil
	}

	var update expression.UpdateBuilder
	if options.Name != nil {
		update = update.Set(
			expression.Name(NameAttribute),
			expression.Value(*options.Name))
	}
	if options.Description != nil {
		update = update.Set(
			expression.Name(DescriptionAttribute),
			expression.Value(*options.Description))
	}

	expr, err := expression.NewBuilder().
		WithCondition(expression.Name(lp.Config.LegacyPermissionsHashKey.Get()).AttributeExists()).
		WithUpdate(update).
		Build()
	if err != nil {
		return err
	}

	if _, err := lp.DynamoDB.UpdateItemWithContext(ctx, &dynamodb.UpdateItemInput{
		ConditionExpression:       expr.Condition(),
		ExpressionAttributeNames:  expr.Names(),
		ExpressionAttributeValues: expr.Values(),
		Key: map[string]*dynamodb.AttributeValue{
			lp.Config.LegacyPermissionsHashKey.Get(): {S: aws.String(id)},
		},
		TableName:        aws.String(lp.Config.LegacyPermissionsTableName.Get()),
		UpdateExpression: expr.Update(),
	}); err != nil {
		if awsutil.IsStatusCode(err, dynamodb.ErrCodeConditionalCheckFailedException) {
			return ErrLegacyPermissionDoesNotExist{ID: id}
		}
		return err
	}

	return nil
}

// Delete a legacy permission
func (lp *LegacyPermissions) Delete(ctx context.Context, id string) error {
	_, err := lp.DynamoDB.DeleteItemWithContext(ctx, &dynamodb.DeleteItemInput{
		Key: map[string]*dynamodb.AttributeValue{
			lp.Config.LegacyPermissionsHashKey.Get(): {S: aws.String(id)},
		},
		TableName: aws.String(lp.Config.LegacyPermissionsTableName.Get()),
	})
	return err
}

// AddRole ...
func (lp *LegacyPermissions) AddRole(ctx context.Context, id, roleID, roleName string) error {
	expr, err := expression.NewBuilder().
		WithCondition(expression.Name(lp.Config.LegacyPermissionsHashKey.Get()).AttributeExists()).
		WithUpdate(expression.Set(
			expression.Name(lp.rolePath(roleID)),
			expression.Value(Role(beefcake.LegacyPermission_Role{
				Id:   roleID,
				Name: roleName,
			})),
		)).
		Build()
	if err != nil {
		return err
	}

	if _, err := lp.DynamoDB.UpdateItemWithContext(ctx, &dynamodb.UpdateItemInput{
		ConditionExpression:       expr.Condition(),
		ExpressionAttributeNames:  expr.Names(),
		ExpressionAttributeValues: expr.Values(),
		Key: map[string]*dynamodb.AttributeValue{
			lp.Config.LegacyPermissionsHashKey.Get(): {S: aws.String(id)},
		},
		TableName:        aws.String(lp.Config.LegacyPermissionsTableName.Get()),
		UpdateExpression: expr.Update(),
	}); err != nil {
		if awsutil.IsStatusCode(err, dynamodb.ErrCodeConditionalCheckFailedException) {
			return nil
		}
		return err
	}

	return nil
}

// RemoveRole ...
func (lp *LegacyPermissions) RemoveRole(ctx context.Context, id, roleID string) error {
	expr, err := expression.NewBuilder().
		WithUpdate(expression.Remove(expression.Name(lp.rolePath(roleID)))).
		Build()
	if err != nil {
		return err
	}

	_, err = lp.DynamoDB.UpdateItemWithContext(ctx, &dynamodb.UpdateItemInput{
		ExpressionAttributeNames:  expr.Names(),
		ExpressionAttributeValues: expr.Values(),
		Key: map[string]*dynamodb.AttributeValue{
			lp.Config.LegacyPermissionsHashKey.Get(): {S: aws.String(id)},
		},
		TableName:        aws.String(lp.Config.LegacyPermissionsTableName.Get()),
		UpdateExpression: expr.Update(),
	})

	return err
}
func (lp *LegacyPermissions) rolePath(roleID string) string {
	return RolesAttribute + "." + roleID
}
