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 AuthorizedFieldPublisherGrantsE2ETest struct {
	suite.Suite

	dbConn *postgres.PostgresDB

	iamRoles     []*db.IAMRole
	eventStreams []*db.EventStream
}

func (suite *AuthorizedFieldPublisherGrantsE2ETest) 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)

	// 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", "SERVICE1-ROLE-A-ARN", service1.ID)
	i, err = conn.IAMRoleCreate(ctx, iamRole1a)
	suite.NoError(err)
	iamRole1a.ID = i

	iamRole1b := createIAMRole("service1-role-b", "SERVICE1-ROLE-B-ARN", 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", "SERVICE2-ROLE-ARN", service2.ID)
	i, err = conn.IAMRoleCreate(ctx, iamRole2)
	suite.NoError(err)
	iamRole2.ID = i

	suite.eventStreams = append(eventStreams1, eventStreams2...)
	suite.iamRoles = []*db.IAMRole{iamRole1a, iamRole1b, iamRole2}

}

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

	_, err := writer.Exec(fmt.Sprintf("DELETE FROM %s;", postgres.AuthorizedFieldPublisherGrantsTableName))
	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.AuditLogsTableName))
	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.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 *AuthorizedFieldPublisherGrantsE2ETest) TestAuthorizedFieldPublisherGrantsE2E() {
	t := suite.T()
	ctx := context.Background()

	t.Run("AuthorizedFieldPublisherGrantCreate", func(t *testing.T) {
		t.Run("Success", func(t *testing.T) {
			grant1 := createAuthorizedFieldPublisherGrant(suite.iamRoles[0].ID, suite.eventStreams[0].ID)
			grant2 := createAuthorizedFieldPublisherGrant(suite.iamRoles[0].ID, suite.eventStreams[4].ID)
			grant3 := createAuthorizedFieldPublisherGrant(suite.iamRoles[0].ID, suite.eventStreams[5].ID)
			grant4 := createAuthorizedFieldPublisherGrant(suite.iamRoles[2].ID, suite.eventStreams[4].ID)
			for _, grant := range []*db.AuthorizedFieldPublisherGrant{grant1, grant2, grant3, grant4} {
				i, err := suite.dbConn.AuthorizedFieldPublisherGrantCreate(ctx, grant)
				suite.NoError(err)
				suite.NotEqual(0, i)
			}
		})
		t.Run("FailureDuplicate", func(t *testing.T) {
			grantDuplicate := createAuthorizedFieldPublisherGrant(suite.iamRoles[0].ID, suite.eventStreams[0].ID)
			_, err := suite.dbConn.AuthorizedFieldPublisherGrantCreate(ctx, grantDuplicate)
			suite.Error(err)
		})
	})

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

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

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

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

	t.Run("AuthorizedFieldPublisherGrantsByEventStreamID", func(t *testing.T) {
		tests := []struct {
			in  int
			out int
		}{
			{suite.eventStreams[0].ID, 1},
			{suite.eventStreams[1].ID, 0},
			{suite.eventStreams[2].ID, 0},
			{suite.eventStreams[3].ID, 0},
			{suite.eventStreams[4].ID, 2},
			{suite.eventStreams[5].ID, 1},
		}

		var grants []*db.AuthorizedFieldPublisherGrant
		var err error
		for _, tt := range tests {
			grants, err = suite.dbConn.AuthorizedFieldPublisherGrantsByEventStreamID(ctx, tt.in) // 3 grants created
			suite.NoError(err)
			suite.Len(grants, tt.out)
		}
	})

	t.Run("Lease", func(t *testing.T) {
		grants, err := suite.dbConn.AuthorizedFieldPublisherGrantsByIAMRoleID(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.AuthorizedFieldPublisherGrantAcquireLease(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.AuthorizedFieldPublisherGrantReleaseLease(lease)
			suite.NoError(err)
		})

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

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

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

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

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

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

func TestAuthorizedFieldPublisherGrantE2E(t *testing.T) {
	suite.Run(t, &AuthorizedFieldPublisherGrantsE2ETest{})
}

func createAuthorizedFieldPublisherGrant(iamRoleID, eventStreamID int) *db.AuthorizedFieldPublisherGrant {
	return &db.AuthorizedFieldPublisherGrant{
		IAMRoleID:     iamRoleID,
		EventStreamID: eventStreamID,
		KMSGrantID:    "",
	}
}
