package manager

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/dynamodb"
)

// DeleteSecretInput Represents the input of a DeleteSecret operation.
type DeleteSecretInput struct {
	// Secret to be deleted
	Name       string
	ActionUser string
}

// DeleteSecretOutput Represents the output of a DeleteSecret operation.
type DeleteSecretOutput struct{}

func (input *DeleteSecretInput) validate() (err error) {
	if input.Name == "" {
		err = &InputValidationError{"invalid input, secret name cannot be empty"}
	}
	return
}

// Delete deletes a secret
func (m *Manager) Delete(secretName string) (err error) {
	input := &DeleteSecretInput{
		Name:       secretName,
		ActionUser: m.Config.ActionUser,
	}
	_, err = m.DeleteSecret(input)
	return
}

// DeleteSecret deletes a secret
func (m *Manager) DeleteSecret(input *DeleteSecretInput) (out *DeleteSecretOutput, err error) {

	err = m.statter.WithMeasuredResult("secret.delete", nil, func() (err error) {
		err = input.validate()
		if err != nil {
			return
		}

		// verify that secret exists, if not throw an error
		queryInput := m.inputForQueryGet(input.Name)
		queryOutput, err := m.dynamoDB().Query(queryInput)

		if err != nil {
			return
		}

		if *queryOutput.Count == 0 {
			return
		}

		secret, err := unmarshalSecret(queryOutput.Items[0])
		if err != nil {
			return
		}

		if input.ActionUser != "" {
			secret.ActionUser = input.ActionUser
		}

		// insert tombstone
		tombstoneInput := m.inputForTombstoneUpdate(secret, true)
		_, err = m.dynamoDB().UpdateItem(tombstoneInput)

		if err != nil {
			return
		}

		// delete from main table
		deleteInput := m.inputForItemDelete(input.Name)
		_, err = m.dynamoDB().DeleteItem(deleteInput)

		if err != nil {
			return
		}
		m.cache.Delete(input.Name)
		return
	})

	return
}

func (m *Manager) removeTombstone(secret *Secret) error {
	tombstoneInput := m.inputForTombstoneUpdate(secret, false)
	_, err := m.dynamoDB().UpdateItem(tombstoneInput)
	return err
}

func (m *Manager) inputForItemDelete(name string) *dynamodb.DeleteItemInput {
	return &dynamodb.DeleteItemInput{
		Key: map[string]*dynamodb.AttributeValue{
			"name": {
				S: aws.String(name),
			},
		},
		ReturnConsumedCapacity: aws.String("INDEXES"),
		TableName:              aws.String(m.Config.stack().SecretsTableName()),
	}
}

func (m *Manager) inputForQueryGet(name string) *dynamodb.QueryInput {
	return &dynamodb.QueryInput{
		ConsistentRead: aws.Bool(true),
		ExpressionAttributeNames: map[string]*string{
			"#N": aws.String("name"),
		},
		ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
			":name": {
				S: aws.String(name),
			},
		},
		KeyConditionExpression: aws.String("#N = :name"),
		ReturnConsumedCapacity: aws.String("INDEXES"),
		TableName:              aws.String(m.Config.stack().SecretsTableName()),
	}
}

func (m *Manager) inputForTombstoneUpdate(secret *Secret, tombstone bool) *dynamodb.UpdateItemInput {
	serviceName := m.Config.ServiceName
	if serviceName == "" {
		serviceName = unknownServiceName
	}
	expressionNames := map[string]*string{
		"#tombstone":    aws.String(dynamoDBKeyTombstone),
		"#service_name": aws.String(dynamoDBKeyServiceName),
		"#action_user":  aws.String(dynamoDBKeyActionUser),
	}

	actionUser := secret.ActionUser
	if actionUser == "" {
		actionUser = m.Config.ActionUser
	}
	expressionValues := map[string]*dynamodb.AttributeValue{
		":tombstone":    &dynamodb.AttributeValue{BOOL: aws.Bool(tombstone)},
		":service_name": &dynamodb.AttributeValue{S: aws.String(serviceName)},
		":action_user":  &dynamodb.AttributeValue{S: aws.String(actionUser)},
	}

	updateExpressionFormat := "#%s = :%[1]s"
	updateExpressionkeys := []string{
		dynamoDBKeyTombstone,
		dynamoDBKeyServiceName,
		dynamoDBKeyActionUser,
	}
	var ueks []string
	for _, key := range updateExpressionkeys {
		ueks = append(ueks, fmt.Sprintf(updateExpressionFormat, key))
	}

	return &dynamodb.UpdateItemInput{
		Key: map[string]*dynamodb.AttributeValue{
			"name":       &dynamodb.AttributeValue{S: aws.String(secret.Name)},
			"updated_at": &dynamodb.AttributeValue{N: aws.String(strconv.FormatInt(secret.UpdatedAt, 10))},
		},
		ExpressionAttributeNames:  expressionNames,
		ExpressionAttributeValues: expressionValues,
		UpdateExpression:          aws.String(fmt.Sprintf("SET %s", strings.Join(ueks, ", "))),
		ReturnConsumedCapacity:    aws.String("INDEXES"),
		TableName:                 aws.String(m.Config.stack().AuditTableName()),
	}
}
