package eventstreams

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/twitchtv/twirp"

	"code.justin.tv/eventbus/controlplane/internal/clients/mocks"
	"code.justin.tv/eventbus/controlplane/internal/clients/servicecatalog"
	"code.justin.tv/eventbus/controlplane/internal/db"
	dbMocks "code.justin.tv/eventbus/controlplane/internal/db/mocks"
	"code.justin.tv/eventbus/controlplane/internal/ldap"
	twirperrMocks "code.justin.tv/eventbus/controlplane/internal/twirperr/mocks"
	"code.justin.tv/eventbus/controlplane/rpc"
)

func TestGenPublisherInfo(t *testing.T) {
	ctx := context.Background()

	service1 := &db.Service{
		Name:             "LocalServiceName1",
		ServiceCatalogID: "1",
	}
	service2 := &db.Service{
		Name:             "LocalServiceName2",
		ServiceCatalogID: "2",
	}

	mockSC := &mocks.Client{}
	mockSC.On("Get", ctx, service1.ServiceCatalogID).Return(&servicecatalog.Response{Name: "SCServiceName1"}, nil)
	mockSC.On("Get", ctx, service2.ServiceCatalogID).Return(nil, errors.New("problem reaching service catalog"))

	s := &EventStreamsService{
		ServiceCatalog: mockSC,
	}

	info := s.genPublisherInfo(ctx, []*db.Service{service1, service2})

	assert.Len(t, info, 2)
	assert.Equal(t, "SCServiceName1", info[0].Name)
	assert.Equal(t, "LocalServiceName2", info[1].Name)
}

func TestEventTypesTwirp(t *testing.T) {
	eventTypes := []*db.EventType{
		&db.EventType{
			ID:        1,
			LDAPGroup: "a",
		},
		&db.EventType{
			ID:        2,
			LDAPGroup: "b",
		},
	}

	t.Run("List", func(t *testing.T) {
		mockDB := &dbMocks.DB{}
		mockDB.On("EventTypes", mock.Anything).Return(eventTypes, nil)
		t.Run("no filter", func(t *testing.T) {
			ctx := context.Background()
			s := &EventTypesService{
				DB: mockDB,
			}
			resp, err := s.List(ctx, &rpc.ListEventTypesReq{})
			assert.NoError(t, err)
			assert.NotNil(t, resp.EventTypes)
			assert.Equal(t, 2, len(resp.EventTypes))
		})

		t.Run("with filter", func(t *testing.T) {
			ctx := context.Background()
			s := &EventTypesService{
				DB: mockDB,
			}
			resp, err := s.List(ctx, &rpc.ListEventTypesReq{
				LdapGroup: "a",
			})
			assert.NoError(t, err)
			assert.NotNil(t, resp.EventTypes)
			assert.Equal(t, 1, len(resp.EventTypes))
		})
	})

	t.Run("ListAuthorizedFieldSubscriberGrantsByEventType", func(t *testing.T) {
		t.Run("retrieves all subscriber grants", func(t *testing.T) {
			mockDB := &dbMocks.DB{}
			et := &db.EventType{
				ID: 1,
			}
			es := []*db.EventStream{
				&db.EventStream{
					ID: 1,
				},
				&db.EventStream{
					ID: 2,
				},
			}
			afs := []*db.AuthorizedField{
				&db.AuthorizedField{
					ID: 3,
				},
				&db.AuthorizedField{
					ID: 4,
				},
			}
			sgs := []*db.AuthorizedFieldSubscriberGrant{
				&db.AuthorizedFieldSubscriberGrant{
					ID: 5,
				},
				&db.AuthorizedFieldSubscriberGrant{
					ID: 6,
				},
			}
			ir := &db.IAMRole{
				ID: 7,
			}
			srv := &db.Service{
				ID: 8,
			}

			mockDB.On("EventTypeByName", mock.Anything, mock.Anything).Return(et, nil)
			mockDB.On("EventStreamsByEventTypeID", mock.Anything, mock.Anything).Return(es, nil)
			mockDB.On("AuthorizedFieldsByEventStreamID", mock.Anything, mock.Anything).Return(afs, nil)
			mockDB.On("AuthorizedFieldSubscriberGrantsByAuthorizedField", mock.Anything, mock.Anything).Return(sgs, nil)
			mockDB.On("IAMRoleByID", mock.Anything, mock.Anything).Return(ir, nil)
			mockDB.On("ServiceByID", mock.Anything, mock.Anything).Return(srv, nil)

			ctx := ldap.WithGroups(context.Background(), []string{"team-eventbus"})
			s := &EventTypesService{
				DB: mockDB,
			}
			resp, err := s.ListAuthorizedFieldSubscriberGrantsByEventType(ctx, &rpc.ListAuthorizedFieldSubscriberGrantsByEventTypeReq{
				EventTypeName: "event-type",
			})
			assert.NoError(t, err)
			assert.Equal(t, 8, len(resp.Grants))
		})

		t.Run("errors when invalid event provided", func(t *testing.T) {
			mockDB := &dbMocks.DB{}
			mockDB.On("EventTypeByName", mock.Anything, mock.Anything).Return(nil, twirperrMocks.NewDBErrorFor("EventTypeNotFound"))
			ctx := context.Background()
			s := &EventTypesService{
				DB: mockDB,
			}
			_, err := s.ListAuthorizedFieldSubscriberGrantsByEventType(ctx, &rpc.ListAuthorizedFieldSubscriberGrantsByEventTypeReq{
				EventTypeName: "event-type",
			})
			assert.Error(t, err)
			assert.Equal(t, twirp.NotFound, err.(twirp.Error).Code())
		})
	})

	t.Run("ListAuthorizedFieldPublisherGrantsByEventType", func(t *testing.T) {
		t.Run("retrieves all publisher grants", func(t *testing.T) {
			mockDB := &dbMocks.DB{}
			et := &db.EventType{
				ID: 1,
			}
			es := []*db.EventStream{
				&db.EventStream{
					ID: 1,
				},
				&db.EventStream{
					ID: 2,
				},
			}
			pgs := []*db.AuthorizedFieldPublisherGrant{
				&db.AuthorizedFieldPublisherGrant{
					ID: 5,
				},
				&db.AuthorizedFieldPublisherGrant{
					ID: 6,
				},
			}
			ir := &db.IAMRole{
				ID: 7,
			}
			srv := &db.Service{
				ID: 8,
			}

			mockDB.On("EventTypeByName", mock.Anything, mock.Anything).Return(et, nil)
			mockDB.On("EventStreamsByEventTypeID", mock.Anything, mock.Anything).Return(es, nil)
			mockDB.On("AuthorizedFieldPublisherGrantsByEventStreamID", mock.Anything, mock.Anything).Return(pgs, nil)
			mockDB.On("IAMRoleByID", mock.Anything, mock.Anything).Return(ir, nil)
			mockDB.On("ServiceByID", mock.Anything, mock.Anything).Return(srv, nil)

			ctx := ldap.WithGroups(context.Background(), []string{"team-eventbus"})
			s := &EventTypesService{
				DB: mockDB,
			}
			resp, err := s.ListAuthorizedFieldPublisherGrantsByEventType(ctx, &rpc.ListAuthorizedFieldPublisherGrantsByEventTypeReq{
				EventTypeName: "event-type",
			})
			assert.NoError(t, err)
			assert.Equal(t, 4, len(resp.Grants))
		})

		t.Run("errors when invalid event provided", func(t *testing.T) {
			mockDB := &dbMocks.DB{}
			mockDB.On("EventTypeByName", mock.Anything, mock.Anything).Return(nil, twirperrMocks.NewDBErrorFor("EventTypeNotFound"))
			ctx := ldap.WithGroups(context.Background(), []string{"team-eventbus"})
			s := &EventTypesService{
				DB: mockDB,
			}
			_, err := s.ListAuthorizedFieldPublisherGrantsByEventType(ctx, &rpc.ListAuthorizedFieldPublisherGrantsByEventTypeReq{
				EventTypeName: "event-type",
			})
			assert.Error(t, err)
			assert.Equal(t, twirp.NotFound, err.(twirp.Error).Code())
		})
	})
}

func TestEventStreamTwirp(t *testing.T) {
	eventStreams := []*db.EventStream{
		&db.EventStream{
			ID: 1,
		},
		&db.EventStream{
			ID: 2,
		},
	}
	mockDB := &dbMocks.DB{}
	mockDB.On("EventStreams", mock.Anything).Return(eventStreams, nil)
	mockDB.On("EventStreamsByName", mock.Anything, mock.Anything).Return(eventStreams[0:1], nil)
	mockDB.On("AuthorizedFields", mock.Anything).Return(make([]*db.AuthorizedField, 0), nil)

	t.Run("List", func(t *testing.T) {
		t.Run("no filter", func(t *testing.T) {
			ctx := context.Background()
			s := &EventStreamsService{
				DB: mockDB,
			}
			resp, err := s.List(ctx, &rpc.ListEventStreamsReq{})
			assert.NoError(t, err)
			assert.NotNil(t, resp.EventStreams)
			assert.Equal(t, 2, len(resp.EventStreams))
		})

		t.Run("with filter", func(t *testing.T) {
			ctx := context.Background()
			s := &EventStreamsService{
				DB: mockDB,
			}
			resp, err := s.List(ctx, &rpc.ListEventStreamsReq{
				EventTypeName: "1",
			})
			assert.NoError(t, err)
			assert.NotNil(t, resp.EventStreams)
			assert.Equal(t, 1, len(resp.EventStreams))
		})
	})
}
