package suite

import (
	"context"

	"code.justin.tv/eventbus/controlplane/e2e/internal/e2eutil"
	"code.justin.tv/eventbus/controlplane/e2e/internal/resource"
	"code.justin.tv/eventbus/controlplane/infrastructure/validation"
	"code.justin.tv/eventbus/controlplane/internal/e2eaccounts"
	"code.justin.tv/eventbus/controlplane/internal/sqsutil"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sqs"
	"github.com/pkg/errors"
)

// Unsubscribing an SQS queue from an SNS topic prevents resubscribing to that topic for 72
// hours, unless the unsubscribe is done from the subscribing account. This test verifies that
// we don't regress the ability to unsubscribe and resubscribe.

var _ Runner = &QueueValidationTestSuite{}

type QueueValidationTestSuite struct {
	*DefaultTestSuite // Use the default suite as ground work
}

func NewQueueValidationTestSuite(suiteName string) (Runner, error) {
	defaultTest, err := NewDefaultTestSuite(suiteName)
	if err != nil {
		return nil, err
	}
	return &QueueValidationTestSuite{
		DefaultTestSuite: defaultTest,
	}, nil
}

func (r *QueueValidationTestSuite) Test(ctx context.Context) {
	var err error
	r.Log(ctx, "Validating subscription target sqs configurations")

	r.Log(ctx, "Checking queue with no configuration errors")
	err = r.checkQueueOk(ctx, r.SubscriptionTargets[0])
	if err != nil {
		r.Error(ctx, "validation failed expectations", err)
		return
	}

	r.Log(ctx, "Checking queue missing KMS encryption")
	err = r.checkQueueMissingKMS(ctx, r.SubscriptionTargets[1])
	if err != nil {
		r.Error(ctx, "validation failed expectations", err)
		return
	}

	r.Log(ctx, "Checking queue missing proper policy")
	err = r.checkQueueMissingPolicy(ctx, r.SubscriptionTargets[2])
	if err != nil {
		r.Error(ctx, "validation failed expectations", err)
		return
	}

	r.Log(ctx, "Checking queue missing DLQ KMS")
	err = r.checkQueueDeadletterMissingKMS(ctx, r.SubscriptionTargets[3])
	if err != nil {
		r.Error(ctx, "validation failed expectations", err)
		return
	}
}

func (r *QueueValidationTestSuite) checkQueueOk(ctx context.Context, target *resource.SubscriptionTarget) error {
	reports, err := e2eutil.Validate(ctx)
	if err != nil {
		return errors.Wrap(err, "validation routine failed")
	}

	validationFailures := validation.NotOk(reports)
	if validationFailures != nil && e2eutil.ErrorsFor(target.Name, validationFailures) {
		return errors.Wrap(err, "expected no errors for queue "+target.Name)
	}
	return nil
}

func (r *QueueValidationTestSuite) checkQueueMissingKMS(ctx context.Context, target *resource.SubscriptionTarget) error {
	sqsClient := sqs.New(session.Must(session.NewSession()), e2eaccounts.AccountCredentials().SubscriberConfig())
	_, err := sqsClient.SetQueueAttributesWithContext(ctx, &sqs.SetQueueAttributesInput{
		Attributes: aws.StringMap(map[string]string{"KmsMasterKeyId": ""}),
		QueueUrl:   aws.String(target.QueueURL),
	})
	if err != nil {
		return errors.Wrap(err, "could not unset kms key id for queue "+target.Name)
	}

	reports, err := e2eutil.Validate(ctx)
	if err != nil {
		return errors.Wrap(err, "validation routine failed")
	}

	failures := validation.NotOk(reports)
	if failures == nil {
		return errors.Wrap(err, "expected validation error")
	} else if !e2eutil.ErrorsFor(target.Name, failures) {
		return errors.Wrap(err, "expected exactly 1 validation error for queue "+target.Name)
	}
	return nil
}

func (r *QueueValidationTestSuite) checkQueueMissingPolicy(ctx context.Context, target *resource.SubscriptionTarget) error {
	sqsClient := sqs.New(session.Must(session.NewSession()), e2eaccounts.AccountCredentials().SubscriberConfig())
	// Remove condition segment to make it malformed
	_, err := sqsClient.SetQueueAttributesWithContext(ctx, &sqs.SetQueueAttributesInput{
		Attributes: aws.StringMap(map[string]string{sqsutil.KeyPolicy: `
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "eventbus",
			"Effect": "Allow",
			"Principal": "*",
			"Action": "sqs:SendMessage",
			"Resource": "*"
		}
	]
}
`,
		}),
		QueueUrl: aws.String(target.QueueURL),
	})
	if err != nil {
		return errors.Wrap(err, "could not unset policy for queue "+target.Name)
	}

	reports, err := e2eutil.Validate(ctx)
	if err != nil {
		return errors.Wrap(err, "validation routine failed")
	}

	failures := validation.NotOk(reports)
	if failures == nil {
		return errors.Wrap(err, "expected validation error")
	} else if !e2eutil.ErrorsFor(target.Name, failures) {
		return errors.Wrap(err, "expected exactly 1 validation error for queue "+target.Name)
	}
	return nil
}

func (r *QueueValidationTestSuite) checkQueueDeadletterMissingKMS(ctx context.Context, target *resource.SubscriptionTarget) error {
	sqsClient := sqs.New(session.Must(session.NewSession()), e2eaccounts.AccountCredentials().SubscriberConfig())
	_, err := sqsClient.SetQueueAttributesWithContext(ctx, &sqs.SetQueueAttributesInput{
		Attributes: aws.StringMap(map[string]string{"KmsMasterKeyId": ""}),
		QueueUrl:   aws.String(target.DeadletterQueueURL),
	})
	if err != nil {
		return errors.Wrap(err, "could not unset kms key id for queue")
	}

	reports, err := e2eutil.Validate(ctx)
	if err != nil {
		return errors.Wrap(err, "validation routine failed")
	}

	failures := validation.NotOk(reports)
	if failures == nil {
		return errors.Wrap(err, "expected validation error")
	} else if !e2eutil.ErrorsFor(target.Name, failures) {
		return errors.Wrap(err, "expected exactly 1 validation error for queue "+target.Name)
	}
	return nil
}

func (r *QueueValidationTestSuite) TestName() string {
	return "QueueValidationTestSuite"
}
