package e2e

import (
	"context"
	"fmt"
	"testing"
	"time"

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

type AuthorizedFieldSubscriberGrantsE2ETest struct {
	suite.Suite

	dbConn *postgres.PostgresDB

	iamRoles         []*db.IAMRole
	authorizedFields []*db.AuthorizedField
}

func (suite *AuthorizedFieldSubscriberGrantsE2ETest) SetupTest() {
	ctx := context.Background()
	conn, err := newLocalDB()
	suite.NoError(err, "no db connection created in EventTypesE2ETest")
	suite.dbConn = conn
	environments := []string{environment.Development, environment.Staging, environment.Production}

	var i int

	// Prerequisite seed data
	// event types and streams
	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)

	// authorized fields
	authField1 := createAuthorizedField(eventStreams1[0].ID, "SecretMessage", "SecretField")
	i, err = conn.AuthorizedFieldCreate(ctx, authField1)
	suite.NoError(err)
	authField1.ID = i

	authField2 := createAuthorizedField(eventStreams1[0].ID, "Classified", "PrivilegedEyesOnly")
	i, err = conn.AuthorizedFieldCreate(ctx, authField2)
	suite.NoError(err)
	authField2.ID = i

	authField3 := createAuthorizedField(eventStreams2[0].ID, "Foo", "Bar")
	i, err = conn.AuthorizedFieldCreate(ctx, authField3)
	suite.NoError(err)
	authField3.ID = i

	// services and iam roles
	service1 := createService("service1", "team-awesome", "", "URL1")
	i, err = conn.ServiceCreate(ctx, service1)
	suite.NoError(err)
	service1.ID = i

	iamRole1a := createIAMRole("SERVICE1-ROLE-A-ARN", "service1-role-a", service1.ID)
	i, err = conn.IAMRoleCreate(ctx, iamRole1a)
	suite.NoError(err)
	iamRole1a.ID = i

	iamRole1b := createIAMRole("SERVICE1-ROLE-B-ARN", "service1-role-b", service1.ID)
	i, err = conn.IAMRoleCreate(ctx, iamRole1b)
	suite.NoError(err)
	iamRole1b.ID = i

	service2 := createService("service2", "team-cool", "", "URL2")
	i, err = conn.ServiceCreate(ctx, service2)
	suite.NoError(err)
	service2.ID = i

	iamRole2 := createIAMRole("SERVICE2-ROLE-ARN", "service2-role", service2.ID)
	i, err = conn.IAMRoleCreate(ctx, iamRole2)
	suite.NoError(err)
	iamRole2.ID = i

	suite.authorizedFields = []*db.AuthorizedField{authField1, authField2, authField3}
	suite.iamRoles = []*db.IAMRole{iamRole1a, iamRole1b, iamRole2}

}

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

	_, err := writer.Exec(fmt.Sprintf("DELETE FROM %s;", postgres.AuthorizedFieldSubscriberGrantsTableName))
	suite.NoError(err)
	_, err = writer.Exec(fmt.Sprintf("DELETE FROM %s;", postgres.IAMRolesTableName))
	suite.NoError(err)
	_, err = writer.Exec(fmt.Sprintf("DELETE FROM %s;", postgres.ServicesTableName))
	suite.NoError(err)
	_, 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 = writer.Exec(fmt.Sprintf("DELETE FROM %s;", postgres.LeasesTableName))
	suite.NoError(err)

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

func (suite *AuthorizedFieldSubscriberGrantsE2ETest) TestAuthorizedFieldSubscriberGrantsE2E() {
	t := suite.T()
	ctx := context.Background()

	t.Run("AuthorizedFieldSubscriberGrantCreate", func(t *testing.T) {
		t.Run("Success", func(t *testing.T) {
			grant1 := createAuthorizedFieldSubscriberGrant(suite.iamRoles[0].ID, suite.authorizedFields[0].ID)
			grant2 := createAuthorizedFieldSubscriberGrant(suite.iamRoles[0].ID, suite.authorizedFields[1].ID)
			grant3 := createAuthorizedFieldSubscriberGrant(suite.iamRoles[0].ID, suite.authorizedFields[2].ID)
			grant4 := createAuthorizedFieldSubscriberGrant(suite.iamRoles[2].ID, suite.authorizedFields[1].ID)
			for _, grant := range []*db.AuthorizedFieldSubscriberGrant{grant1, grant2, grant3, grant4} {
				i, err := suite.dbConn.AuthorizedFieldSubscriberGrantCreate(ctx, grant)
				suite.NoError(err)
				suite.NotEqual(0, i)
			}
		})
		t.Run("FailureDuplicate", func(t *testing.T) {
			grantDuplicate := createAuthorizedFieldSubscriberGrant(suite.iamRoles[0].ID, suite.authorizedFields[0].ID)
			_, err := suite.dbConn.AuthorizedFieldSubscriberGrantCreate(ctx, grantDuplicate)
			suite.Error(err)
		})
	})

	t.Run("AuthorizedFieldSubscriberGrantByIAMRoleID", func(t *testing.T) {
		var grants []*db.AuthorizedFieldSubscriberGrant
		var err error

		grants, err = suite.dbConn.AuthorizedFieldSubscriberGrantsByIAMRoleID(ctx, suite.iamRoles[0].ID) // 3 grants created
		suite.NoError(err)
		suite.Len(grants, 3)

		grants, err = suite.dbConn.AuthorizedFieldSubscriberGrantsByIAMRoleID(ctx, suite.iamRoles[1].ID) // 0 grants created
		suite.NoError(err)
		suite.Len(grants, 0)

		grants, err = suite.dbConn.AuthorizedFieldSubscriberGrantsByIAMRoleID(ctx, suite.iamRoles[2].ID) // 1 grants created
		suite.NoError(err)
		suite.Len(grants, 1)
	})

	t.Run("Lease", func(t *testing.T) {
		grants, err := suite.dbConn.AuthorizedFieldSubscriberGrantsByIAMRoleID(ctx, suite.iamRoles[0].ID) // 3 grants created
		suite.NoError(err)

		t.Run("Acquire and release", func(t *testing.T) {
			lease, leaseCtx, err := suite.dbConn.AuthorizedFieldSubscriberGrantAcquireLease(ctx, grants[0].ID, 10*time.Minute)
			suite.NotNil(lease)
			require.NotNil(suite.T(), lease)
			require.NoError(suite.T(), err)
			suite.NotNil(leaseCtx)
			suite.NoError(err)

			err = suite.dbConn.AuthorizedFieldSubscriberGrantReleaseLease(lease)
			suite.NoError(err)
		})

		t.Run("Double acquire", func(t *testing.T) {
			lease1, _, err := suite.dbConn.AuthorizedFieldSubscriberGrantAcquireLease(ctx, grants[0].ID, 10*time.Second)
			suite.NotNil(lease1)
			suite.NoError(err)

			lease2, _, err := suite.dbConn.AuthorizedFieldSubscriberGrantAcquireLease(ctx, grants[0].ID, 10*time.Second)
			suite.Nil(lease2)
			suite.Error(err)

			err = suite.dbConn.AuthorizedFieldSubscriberGrantReleaseLease(lease1)
			suite.NoError(err)
		})

		t.Run("Acquire and update infra", func(t *testing.T) {
			lease, _, err := suite.dbConn.AuthorizedFieldSubscriberGrantAcquireLease(ctx, grants[0].ID, 10*time.Second)
			suite.NotNil(lease)
			suite.NoError(err)

			_, err = suite.dbConn.AuthorizedFieldSubscriberGrantUpdateInfra(ctx, lease, grants[0].ID, &db.AuthorizedFieldSubscriberGrantInfraUpdate{KMSGrantID: "GRANT-ID"})
			suite.NoError(err)

			grant, err := suite.dbConn.AuthorizedFieldSubscriberGrantByID(ctx, grants[0].ID)
			suite.NoError(err)
			suite.Equal("GRANT-ID", grant.KMSGrantID)
		})
	})
}

func TestAuthorizedFieldSubscriberGrantE2E(t *testing.T) {
	suite.Run(t, &AuthorizedFieldSubscriberGrantsE2ETest{})
}

func createAuthorizedFieldSubscriberGrant(iamRoleID, authorizedFieldID int) *db.AuthorizedFieldSubscriberGrant {
	return &db.AuthorizedFieldSubscriberGrant{
		IAMRoleID:         iamRoleID,
		AuthorizedFieldID: authorizedFieldID,
		KMSGrantID:        "",
	}
}
