package manager

import (
	"bytes"
	"fmt"
	"time"

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

// XXX should we include a check for writes?

// CheckTable checks if we can call dynamodb.Scan and dynamodb.Query
// on the specified table. Returns nil upon success or the related
// DynamoDB operation error upon failure.
func (m *Manager) CheckTable() error {
	// Scan all tables
	for _, table := range []string{m.TableName(), m.NamespaceTableName(), m.AuditTableName()} {
		scanInput := m.inputForStatusScan(table)
		_, err := m.dynamoDB().Scan(scanInput)
		if err != nil {
			return fmt.Errorf("Can't scan table %s: %s", *scanInput.TableName, err)
		}
	}

	queryInput := m.inputForStatusQuery()
	_, err := m.dynamoDB().Query(queryInput)
	if err != nil {
		return fmt.Errorf("Can't query table %s: %s", *queryInput.TableName, err)
	}

	return nil
}

// CheckKMS checks if the specified key is able to call
// kms.GenerateDataKey and kms.Decrypt, and compares the generated key
// from what it decrypted. Returns nil upon success, returns the
// related KMS error upon failure.
func (m *Manager) CheckKMS() error {
	context := map[string]*string{
		"operation": aws.String("status_check"),
		"timestamp": aws.String(time.Now().Format(time.RFC1123Z)),
	}
	generateDataKeyInput := m.inputForStatusGenerateDataKey(context)
	generateDataKeyOutput, err := m.kms().GenerateDataKey(generateDataKeyInput)
	if err != nil {
		return err
	}

	decryptInput := m.inputForStatusDecrypt(context, generateDataKeyOutput.CiphertextBlob)
	decryptOutput, err := m.kms().Decrypt(decryptInput)
	if err != nil {
		return err
	}
	if !bytes.Equal(decryptOutput.Plaintext, generateDataKeyOutput.Plaintext) {
		return fmt.Errorf("kms.Decrypt output not equal to kms.GenerateDataKey output")
	}

	return nil
}

func (m *Manager) inputForStatusGenerateDataKey(context map[string]*string) *kms.GenerateDataKeyInput {
	return &kms.GenerateDataKeyInput{
		EncryptionContext: context,
		KeySpec:           aws.String("AES_256"),
		KeyId:             aws.String(m.Config.stack().KMSPrimaryKey().KeyARN),
	}
}

func (m *Manager) inputForStatusDecrypt(context map[string]*string, ciphertext []byte) *kms.DecryptInput {
	return &kms.DecryptInput{
		CiphertextBlob:    ciphertext,
		EncryptionContext: context,
	}
}

func (m *Manager) inputForStatusScan(tableName string) *dynamodb.ScanInput {
	return &dynamodb.ScanInput{
		TableName:              aws.String(tableName),
		Limit:                  aws.Int64(1),
		ReturnConsumedCapacity: aws.String("INDEXES"),
	}
}

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

}
