package resource

import (
	"context"
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sqs"

	"code.justin.tv/eventbus/controlplane/e2e/internal/e2eutil"
	"code.justin.tv/eventbus/controlplane/e2e/internal/expected"
	"code.justin.tv/eventbus/controlplane/e2e/internal/httpserver"
	"code.justin.tv/eventbus/controlplane/e2e/internal/report"
	"code.justin.tv/eventbus/controlplane/e2e/internal/test"
	"code.justin.tv/eventbus/controlplane/internal/e2eaccounts"
	"code.justin.tv/eventbus/controlplane/internal/policy"
	"code.justin.tv/eventbus/controlplane/internal/sqsutil"
	"code.justin.tv/eventbus/controlplane/rpc"
)

var _ test.Runner = &SubscriptionTarget{}

type SubscriptionTarget struct {
	test.TestRunner

	ServiceCatalogID   string `json:"service_catalog_id"`
	Name               string `json:"name"`
	QueueURL           string `json:"-"` // set by Setup
	DeadletterQueueURL string `json:"-"`
}

func (st *SubscriptionTarget) TestName() string {
	return fmt.Sprintf(
		"SubscriptionTarget{name=%s,catalogid=%s}",
		st.Name, st.ServiceCatalogID,
	)
}

func (st *SubscriptionTarget) Setup(ctx context.Context) report.Error {
	ctx = e2eutil.AppendTestPath(ctx, st.TestName())
	st.Log(ctx, "Registering subscription target")

	service, err := httpserver.Service(st.ServiceCatalogID)
	if err != nil {
		return report.ErrorFromContext(ctx, "Could not get service for subscription target creation", err)
	}

	byoqResult, err := createBYOQueue(ctx, st.Name)
	if err != nil {
		return report.ErrorFromContext(ctx, "could not make BYOQ queue", err)
	}
	st.QueueURL = byoqResult.QueueURL
	st.DeadletterQueueURL = byoqResult.DeadletterQueueURL

	req := &rpc.CreateTargetReq{
		ServiceId: service.Id,
		Name:      e2eutil.Suffix(st.Name, e2eutil.JobID(ctx)),
		Type:      rpc.TargetType_TARGET_TYPE_SQS,
		Details: &rpc.CreateTargetReq_Sqs{
			Sqs: &rpc.SQSDetails{
				QueueUrl:           byoqResult.QueueURL,
				DeadletterQueueUrl: byoqResult.DeadletterQueueURL,
				DeadletterQueueArn: byoqResult.DeadletterQueueARN,
			},
		},
	}

	httpClient := e2eutil.HTTPClientWithLDAP()
	targetsClient := rpc.NewTargetsProtobufClient(expected.TwirpEndpoint, httpClient)
	_, err = targetsClient.Create(ctx, req)
	if err != nil {
		return report.ErrorFromContext(ctx, "Could not create subscription target in database", err)
	}
	return nil
}

func (st *SubscriptionTarget) Test(ctx context.Context) {
	ctx = e2eutil.AppendTestPath(ctx, st.TestName())
	st.Log(ctx, "Beginning SubscriptionTarget tests")

	// base checks
	st.sqsExistenceCheck(ctx)
	st.sqsPolicyCheck(ctx)

	// optional tests are conditionally run here, if specified in the seed data
}

func (st *SubscriptionTarget) sqsExistenceCheck(ctx context.Context) {
	ctx = e2eutil.AppendTestPath(ctx, "SQSExistence")
	st.Log(ctx, "Checking queue existence")

	target, err := httpserver.Target(st.ServiceCatalogID, st.Name, e2eutil.JobID(ctx))
	if err != nil {
		st.Error(ctx, "Could not fetch subscription target from database", err)
		return
	}

	sess := session.Must(session.NewSession())
	sqsClient := sqs.New(sess, e2eaccounts.AccountCredentials().SubscriberConfig())
	queueURL := target.GetSqs().GetQueueUrl()

	_, err = sqsClient.GetQueueAttributesWithContext(ctx, &sqs.GetQueueAttributesInput{
		QueueUrl: aws.String(queueURL),
	})
	if err != nil {
		st.Error(ctx, "Could not check queue exists", err)
		return
	}

	if deadletterURL := target.GetSqs().GetDeadletterQueueUrl(); deadletterURL != "" {
		_, err = sqsClient.GetQueueAttributesWithContext(ctx, &sqs.GetQueueAttributesInput{
			QueueUrl: aws.String(deadletterURL),
		})
		if err != nil {
			st.Error(ctx, "Could not check deadletter exists", err)
			return
		}
	}
}

func (st *SubscriptionTarget) sqsPolicyCheck(ctx context.Context) {
	ctx = e2eutil.AppendTestPath(ctx, "SQSPolicy")
	st.Log(ctx, "Checking queue policy")

	target, err := httpserver.Target(st.ServiceCatalogID, st.Name, e2eutil.JobID(ctx))
	if err != nil {
		st.Error(ctx, "Could not fetch subscription target from database", err)
		return
	}

	sess := session.Must(session.NewSession())
	sqsClient := sqs.New(sess, e2eaccounts.AccountCredentials().SubscriberConfig())
	queueURL := target.GetSqs().GetQueueUrl()

	queueAttrs, err := sqsClient.GetQueueAttributesWithContext(ctx, &sqs.GetQueueAttributesInput{
		QueueUrl:       aws.String(queueURL),
		AttributeNames: aws.StringSlice([]string{"Policy"}),
	})
	if err != nil {
		st.Error(ctx, "Could not get queue policy", err)
		return
	}

	policyJSON := aws.StringValue(queueAttrs.Attributes[sqsutil.KeyPolicy])

	var queuePolicy policy.Policy
	if err = json.Unmarshal([]byte(policyJSON), &queuePolicy); err != nil {
		st.Error(ctx, "Could not unmarshal queue policy", err)
		return
	}

	defaultQueuePolicy := policy.DefaultQueuePolicy(e2eaccounts.MainAccountID)

	queuePolicyHasDefaultStatement := reflect.DeepEqual(defaultQueuePolicy.Statement, queuePolicy.Statement)
	if !queuePolicyHasDefaultStatement {
		st.Error(ctx, "Queue policy has the wrong statement", err)
		return
	}
}

func (st *SubscriptionTarget) Clean(ctx context.Context) []report.Error {
	ctx = e2eutil.AppendTestPath(ctx, st.TestName())
	st.Log(ctx, "Deleting subscription target")

	foundTarget, err := httpserver.Target(st.ServiceCatalogID, st.Name, e2eutil.JobID(ctx))
	if err != nil {
		return reportErrorSlice(ctx, "Could not get target from database", err)
	}

	sess := session.Must(session.NewSession())
	sqsClient := sqs.New(sess, e2eaccounts.AccountCredentials().SubscriberConfig())
	queueURL := foundTarget.GetSqs().GetQueueUrl()
	_, err = sqsClient.DeleteQueueWithContext(context.Background(), &sqs.DeleteQueueInput{
		QueueUrl: aws.String(queueURL),
	})
	if err != nil {
		return reportErrorSlice(ctx, "Could not delete SQS queue "+queueURL, err)
	}

	deadletterURL := foundTarget.GetSqs().GetDeadletterQueueUrl()
	if deadletterURL != "" {
		_, err = sqsClient.DeleteQueueWithContext(ctx, &sqs.DeleteQueueInput{
			QueueUrl: aws.String(deadletterURL),
		})
		if err != nil {
			return reportErrorSlice(ctx, "Could not delete SQS deadletter queue "+deadletterURL, err)
		}
	}

	return nil
}
