package e2e

import (
	"context"
	"fmt"
	"testing"

	"code.justin.tv/eventbus/controlplane/internal/db"
	"code.justin.tv/eventbus/controlplane/internal/db/postgres"
	"code.justin.tv/eventbus/controlplane/internal/environment"
	"code.justin.tv/eventbus/controlplane/internal/twirperr"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/suite"
)

type AuthorizedFieldsE2ETest struct {
	suite.Suite

	dbConn *postgres.PostgresDB

	eventStreams []*db.EventStream
}

func (suite *AuthorizedFieldsE2ETest) SetupTest() {
	ctx := context.Background()
	conn, err := newLocalDB()
	suite.NoError(err, "no db connection created in EventTypesE2ETest")
	suite.dbConn = conn

	// Prerequisite event types and streams
	environments := []string{environment.Development, environment.Staging, environment.Production}
	eventType1 := createEventType("CoolEvent", "Fires when cool stuff happens", "SCHEMA", "repopath")
	eventStreams1, err := conn.EventTypeAndStreamsCreate(ctx, eventType1, environments)
	suite.NoError(err)

	eventType2 := createEventType("AmazingUpdate", "Fires when an amazing update occurs", "SCHEMA", "repopath")
	eventStreams2, err := conn.EventTypeAndStreamsCreate(ctx, eventType2, environments)
	suite.NoError(err)

	suite.eventStreams = append(eventStreams1, eventStreams2...)
}

func (suite *AuthorizedFieldsE2ETest) TearDownTest() {
	writer := suite.dbConn.WriterConn()

	_, err := writer.Exec(fmt.Sprintf("DELETE FROM %s;", postgres.AuthorizedFieldsTableName))
	suite.NoError(err)
	_, err = writer.Exec(fmt.Sprintf("DELETE FROM %s;", postgres.EventStreamsTableName))
	suite.NoError(err)
	_, err = writer.Exec(fmt.Sprintf("DELETE FROM %s;", postgres.EventTypesTableName))
	suite.NoError(err)

	err = suite.dbConn.Close()
	suite.NoError(err)
}

func (suite *AuthorizedFieldsE2ETest) TestAuthorizedFieldsE2E() {
	t := suite.T()
	ctx := context.Background()

	t.Run("AuthorizedFieldCreate", func(t *testing.T) {
		t.Run("Success", func(t *testing.T) {
			authField1 := createAuthorizedField(suite.eventStreams[0].ID, "SecretMessage", "SecretField")
			authField2 := createAuthorizedField(suite.eventStreams[0].ID, "ShhhDontTell", "DecryptMe")
			authField3 := createAuthorizedField(suite.eventStreams[0].ID, "IAmPrivate", "ClassifiedField")
			authField4 := createAuthorizedField(suite.eventStreams[2].ID, "Womp", "Womp")
			authField5 := createAuthorizedField(suite.eventStreams[5].ID, "Hello", "Hola")
			for _, authField := range []*db.AuthorizedField{authField1, authField2, authField3, authField4, authField5} {
				_, err := suite.dbConn.AuthorizedFieldCreate(ctx, authField)
				suite.NoError(err)
			}
		})
		t.Run("FailureDuplicate", func(t *testing.T) {
			authFieldDuplicate := createAuthorizedField(suite.eventStreams[0].ID, "SecretMessage", "SecretField")
			_, err := suite.dbConn.AuthorizedFieldCreate(ctx, authFieldDuplicate)
			suite.Error(err)
		})
	})

	t.Run("AuthorizedFields", func(t *testing.T) {
		authFields, err := suite.dbConn.AuthorizedFields(ctx)
		suite.NoError(err)
		suite.Len(authFields, 5)

		found := false
		// ensure returned entries have all the right fields set
		for _, authField := range authFields {
			if authField.EventStreamID == suite.eventStreams[0].ID && authField.MessageName == "SecretMessage" && authField.FieldName == "SecretField" {
				found = true
			}
		}
		suite.True(found)
	})

	t.Run("AuthorizedFieldsByEventStreamID", func(t *testing.T) {
		verifyAuthorizedFieldsByEventStreamID := func(eventStreamID int, expectedLen int) {
			authFields, err := suite.dbConn.AuthorizedFieldsByEventStreamID(ctx, eventStreamID)
			suite.NoError(err)
			suite.Len(authFields, expectedLen)
			for _, authField := range authFields {
				suite.Equal(eventStreamID, authField.EventStreamID)
			}
		}

		// aliasing based on test setup structure
		eventStreamWithThreeAuthFields := suite.eventStreams[0]
		eventStreamWithOneAuthField1 := suite.eventStreams[2]
		eventStreamWithOneAuthField2 := suite.eventStreams[5]
		eventStreamWithNoAuthFields1 := suite.eventStreams[1]
		eventStreamWithNoAuthFields2 := suite.eventStreams[3]
		eventStreamWithNoAuthFields3 := suite.eventStreams[4]

		verifyAuthorizedFieldsByEventStreamID(eventStreamWithThreeAuthFields.ID, 3)
		verifyAuthorizedFieldsByEventStreamID(eventStreamWithOneAuthField1.ID, 1)
		verifyAuthorizedFieldsByEventStreamID(eventStreamWithOneAuthField2.ID, 1)
		verifyAuthorizedFieldsByEventStreamID(eventStreamWithNoAuthFields1.ID, 0)
		verifyAuthorizedFieldsByEventStreamID(eventStreamWithNoAuthFields2.ID, 0)
		verifyAuthorizedFieldsByEventStreamID(eventStreamWithNoAuthFields3.ID, 0)
	})

	t.Run("AuthorizedFieldsByAuthContext", func(t *testing.T) {
		var authField *db.AuthorizedField
		var err error

		t.Run("Success", func(t *testing.T) {
			authField, err = suite.dbConn.AuthorizedFieldByAuthContext(ctx, suite.eventStreams[0].ID, "SecretMessage", "SecretField")
			suite.NoError(err)
			suite.Equal("SecretMessage", authField.MessageName)
			suite.Equal("SecretField", authField.FieldName)

			authField, err = suite.dbConn.AuthorizedFieldByAuthContext(ctx, suite.eventStreams[2].ID, "Womp", "Womp")
			suite.NoError(err)
			suite.Equal("Womp", authField.MessageName)
			suite.Equal("Womp", authField.FieldName)

			authField, err = suite.dbConn.AuthorizedFieldByAuthContext(ctx, suite.eventStreams[5].ID, "Hello", "Hola")
			suite.NoError(err)
			suite.Equal("Hello", authField.MessageName)
			suite.Equal("Hola", authField.FieldName)
		})

		t.Run("FailureNotFound", func(t *testing.T) {
			authField, err = suite.dbConn.AuthorizedFieldByAuthContext(ctx, suite.eventStreams[0].ID, "DoesNot", "Exist")
			suite.Nil(authField)
			suite.Error(err)
			suite.True(errors.Cause(err).(twirperr.DBError).AuthorizedFieldNotFound())

			authField, err = suite.dbConn.AuthorizedFieldByAuthContext(ctx, suite.eventStreams[2].ID, "SecretMessage", "SecretField")
			suite.Nil(authField)
			suite.Error(err)
			suite.True(errors.Cause(err).(twirperr.DBError).AuthorizedFieldNotFound())

			authField, err = suite.dbConn.AuthorizedFieldByAuthContext(ctx, suite.eventStreams[0].ID, "Womp", "Womp")
			suite.Nil(authField)
			suite.Error(err)
			suite.True(errors.Cause(err).(twirperr.DBError).AuthorizedFieldNotFound())
		})

	})
}

func TestAuthorizedFieldsE2E(t *testing.T) {
	suite.Run(t, &AuthorizedFieldsE2ETest{})
}

func createAuthorizedField(eventStreamID int, messageName, fieldName string) *db.AuthorizedField {
	return &db.AuthorizedField{
		EventStreamID: eventStreamID,
		MessageName:   messageName,
		FieldName:     fieldName,
	}
}
