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"
	"github.com/stretchr/testify/suite"
)

type PublicationsE2ETest struct {
	suite.Suite

	dbConn *postgres.PostgresDB

	eventStreams []*db.EventStream
	accounts     []*db.Account
	iamRoles     []*db.IAMRole
	services     []*db.Service
}

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

	// Prerequisite service (needed to create accounts)
	service1 := createService("S1", "cool-team", "cool service", "123")
	service1ID, err := conn.ServiceCreate(ctx, service1)
	service1.ID = service1ID
	suite.NoError(err)

	service2 := createService("S2", "wow-team", "wow service", "456")
	service2ID, err := conn.ServiceCreate(ctx, service2)
	service2.ID = service2ID
	suite.NoError(err)

	suite.services = []*db.Service{service1, service2}

	// Prerequisite accounts
	suite.accounts = []*db.Account{}
	account1 := createAccount("111111111111", "acct1", service1ID)
	account2 := createAccount("222222222222", "acct2", service1ID)
	account3 := createAccount("333333333333", "acct3", service2ID)
	for _, acct := range []*db.Account{account1, account2, account3} {
		id, err := conn.AccountCreate(ctx, acct)
		suite.NoError(err)
		acct.ID = id
		suite.accounts = append(suite.accounts, acct)
	}

	// Prerequisite IAM roles
	suite.iamRoles = []*db.IAMRole{}
	iamRole1 := createIAMRole("111111111111", "iamRole1", service1ID)
	iamRole2 := createIAMRole("222222222222", "iamRole2", service2ID)
	iamRole3 := createIAMRole("333333333333", "iamRole3", service2ID)
	for _, iamRole := range []*db.IAMRole{iamRole1, iamRole2, iamRole3} {
		id, err := conn.IAMRoleCreate(ctx, iamRole)
		suite.NoError(err)
		iamRole.ID = id
		suite.iamRoles = append(suite.iamRoles, iamRole)
	}
}

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

	_, err := writer.Exec(fmt.Sprintf("DELETE FROM %s;", postgres.PublicationsTableName))
	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.AccountsTableName))
	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 = suite.dbConn.Close()
	suite.NoError(err)
}

func (suite *PublicationsE2ETest) TestPublicationsE2E() {
	t := suite.T()
	ctx := context.Background()

	t.Run("PublicationCreate", func(t *testing.T) {
		pub1 := createPublication(suite.eventStreams[0].ID, &suite.accounts[0].ID, &suite.iamRoles[0].ID)
		pub2 := createPublication(suite.eventStreams[1].ID, &suite.accounts[1].ID, nil)
		pub3 := createPublication(suite.eventStreams[0].ID, &suite.accounts[2].ID, &suite.iamRoles[2].ID)
		pub4 := createPublication(suite.eventStreams[3].ID, nil, &suite.iamRoles[2].ID)
		pub5 := createPublication(suite.eventStreams[4].ID, &suite.accounts[2].ID, nil)
		pub6 := createPublication(suite.eventStreams[3].ID, nil, &suite.iamRoles[1].ID)

		for _, pub := range []*db.Publication{pub1, pub2, pub3, pub4, pub5, pub6} {
			_, err := suite.dbConn.PublicationCreate(ctx, pub)
			suite.NoError(err)
		}
	})

	t.Run("Publications", func(t *testing.T) {
		pubs, err := suite.dbConn.Publications(ctx)
		suite.NoError(err)
		suite.Len(pubs, 6)
	})

	t.Run("PublicationsByServiceID", func(t *testing.T) {
		verifyPublicationsByServiceID := func(serviceID int, expectedLen int) {
			pubs, err := suite.dbConn.PublicationsByServiceID(ctx, serviceID)
			suite.NoError(err)
			suite.Len(pubs, expectedLen)
			for _, pub := range pubs {
				// Find the publication's account and assert that it's the same service ID as the original request
				// ... this is a bit convoluted because theres no query to lookup a single account by ID,
				// so we get them all by service ID and iterate through them to find the one we want
				accts, err := suite.dbConn.AccountsByServiceID(ctx, serviceID)
				suite.NoError(err)
				foundAccount := false
				for _, acct := range accts {
					if pub.AccountID.Valid && int64(acct.ID) == pub.AccountID.Int64 && acct.ServiceID == serviceID {
						foundAccount = true
					}
				}

				// Find the publication's IAM role and assert that it's the same service ID as the original request
				// ... this is a bit convoluted because theres no query to lookup a single IAM role by ID,
				// so we get them all by service ID and iterate through them to find the one we want
				iamRoles, err := suite.dbConn.IAMRolesByServiceID(ctx, serviceID)
				suite.NoError(err)
				foundIAMRole := false
				for _, iamRole := range iamRoles {
					if pub.IAMRoleID.Valid && int64(iamRole.ID) == pub.IAMRoleID.Int64 && iamRole.ServiceID == serviceID {
						foundIAMRole = true
					}
				}

				suite.True(foundAccount || foundIAMRole)
			}
		}
		verifyPublicationsByServiceID(suite.services[0].ID, 2)
		verifyPublicationsByServiceID(suite.services[1].ID, 4)
	})

	t.Run("PublicationsByIAMRoleID", func(t *testing.T) {
		pubs, err := suite.dbConn.PublicationsByIAMRoleID(ctx, suite.iamRoles[2].ID)
		suite.NoError(err)
		suite.Len(pubs, 2)

		pubs, err = suite.dbConn.PublicationsByIAMRoleID(ctx, 999)
		suite.NoError(err)
		suite.Len(pubs, 0)
	})

	t.Run("PublicationsByEventStreamID", func(t *testing.T) {
	})
}

func TestPublicationsE2E(t *testing.T) {
	suite.Run(t, &PublicationsE2ETest{})
}
