package suite

import (
	"context"

	"code.justin.tv/eventbus/controlplane/e2e/internal/e2eutil"
	"code.justin.tv/eventbus/controlplane/e2e/internal/report"
	"code.justin.tv/eventbus/controlplane/e2e/internal/resource"
	"code.justin.tv/eventbus/controlplane/infrastructure/validation"
	"code.justin.tv/eventbus/controlplane/internal/e2eaccounts"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sns"
	"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 = &SubscriptionValidationTestSuite{}

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

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

func (s *SubscriptionValidationTestSuite) Setup(ctx context.Context) report.Error {
	ctx = e2eutil.AppendTestPath(ctx, s.TestName())
	reportErr := s.DefaultTestSuite.Setup(ctx)
	if reportErr != nil {
		return reportErr
	}

	// Delete the 'invalid' subscription to actually make it invalid
	out, err := s.Subscriptions[1].AWSSubscriptionPoll(ctx)
	if err != nil {
		return report.ErrorFromContext(ctx, "unable to get subscription", err)
	}

	sess := session.Must(session.NewSession())
	snsClient := sns.New(sess, e2eaccounts.AccountCredentials().SubscriberConfig())
	_, err = snsClient.UnsubscribeWithContext(ctx, &sns.UnsubscribeInput{
		SubscriptionArn: out.SubscriptionArn,
	})
	if err != nil {
		return report.ErrorFromContext(ctx, "unable to remove subscription for testing", err)
	}

	return nil
}

func (s *SubscriptionValidationTestSuite) Test(ctx context.Context) {
	var err error

	s.Log(ctx, "Checking subscription with no configuration errors")
	err = s.checkSubscriptionOk(ctx, s.Subscriptions[0])
	if err != nil {
		s.Error(ctx, "validation failed expectations", err)
		return
	}

	s.Log(ctx, "Checking subscription that has been wrongly deleted")
	err = s.checkSubscriptionMissing(ctx, s.Subscriptions[1])
	if err != nil {
		s.Error(ctx, "expected validation errors", err)
		return
	}
}

func (s *SubscriptionValidationTestSuite) checkSubscriptionOk(ctx context.Context, subscription *resource.Subscription) 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(subscription.TargetName, validationFailures) {
		return errors.Wrap(err, "expected no errors for subscription")
	}
	return nil
}

func (s *SubscriptionValidationTestSuite) checkSubscriptionMissing(ctx context.Context, subscription *resource.Subscription) error {
	reports, err := e2eutil.Validate(ctx)
	if err != nil {
		return errors.Wrap(err, "validation routine failed")
	}

	validationFailures := validation.NotOk(reports)
	if !e2eutil.ErrorsFor(subscription.TargetName, validationFailures) {
		return errors.New("expected validation error for subscription")
	}

	// Already deleted the subscription as part of the test, so skip its cleaning phase
	subscription.SkipClean(true)

	return nil
}

func (s *SubscriptionValidationTestSuite) TestName() string {
	return "SubscriptionValidationTestSuite"
}
