package ddbtoredis_test

import (
	"context"
	"testing"

	"github.com/golang/mock/gomock"
	"github.com/smartystreets/goconvey/convey"
	"github.com/tidbcloud/api-gateway/ddbtoredis"
	"github.com/tidbcloud/api-gateway/ddbtoredis/mock"
)

func TestSyncer(t *testing.T) {
	convey.Convey("Test Syncer AntiEntropy", t, func() {
		ctrl := gomock.NewController(t)
		defer ctrl.Finish()
		any := gomock.Any()
		ctx := context.Background()
		mockRedisRw := mock.NewMockRedisReaderWriter(ctrl)
		mockDynamodbR := mock.NewMockDynamodbReader(ctrl)
		mockSubscriber := mock.NewMockDynamodbStreamsSubscriber(ctrl)
		syncer, err := ddbtoredis.NewSyncer(mockRedisRw, mockDynamodbR, mockSubscriber)
		convey.So(err, convey.ShouldBeNil)
		convey.Convey("if dynamodb is empty, and redis is not empty, should delete redis", func() {
			mockRedisRw.EXPECT().ListAPIKeys(any).Return(
				map[string]ddbtoredis.APIKey{
					"1": {"a", 1, 0},
					"2": {"b", 2, 1},
				},
				nil,
			)
			mockRedisRw.EXPECT().DeleteAPIKey(any, "1").Times(1)
			mockRedisRw.EXPECT().DeleteAPIKey(any, "2").Times(1)
			mockDynamodbR.EXPECT().ScanRecords(any).Return([]ddbtoredis.KeyDynamodb{}, nil)
			err := syncer.AntiEntropy(ctx)
			convey.So(err, convey.ShouldBeNil)
		})
		convey.Convey("if dynamodb is not empty, and redis is empty, should insert records to redis", func() {
			mockRedisRw.EXPECT().ListAPIKeys(any).Return(
				map[string]ddbtoredis.APIKey{},
				nil,
			)
			mockRedisRw.EXPECT().SetAPIKey(any, "1", "a", 1, 0).Times(1)
			mockRedisRw.EXPECT().SetAPIKey(any, "2", "b", 2, 1).Times(1)
			mockDynamodbR.EXPECT().ScanRecords(any).Return(
				[]ddbtoredis.KeyDynamodb{
					{"1", "a", 1, 0},
					{"2", "b", 2, 1},
				},
				nil,
			)
			err := syncer.AntiEntropy(ctx)
			convey.So(err, convey.ShouldBeNil)
		})
		convey.Convey("if dynamodb and redis are the same, do nothing", func() {
			mockRedisRw.EXPECT().ListAPIKeys(any).Return(
				map[string]ddbtoredis.APIKey{
					"1": {"a", 1, 0},
					"2": {"b", 2, 1},
				},
				nil,
			)
			mockDynamodbR.EXPECT().ScanRecords(any).Return(
				[]ddbtoredis.KeyDynamodb{
					{"1", "a", 1, 0},
					{"2", "b", 2, 1},
				},
				nil,
			)
			err := syncer.AntiEntropy(ctx)
			convey.So(err, convey.ShouldBeNil)
		})
		convey.Convey("if dynamodb and redis are not the same, sync ddb to redis", func() {
			mockRedisRw.EXPECT().ListAPIKeys(any).Return(
				map[string]ddbtoredis.APIKey{
					"1": {"b", 1, 0},
					"2": {"b", 2, 1},
				},
				nil,
			)
			mockDynamodbR.EXPECT().ScanRecords(any).Return(
				[]ddbtoredis.KeyDynamodb{
					{"1", "a", 1, 0},
					{"2", "b", 2, 1},
				},
				nil,
			)
			mockRedisRw.EXPECT().SetAPIKey(any, "1", "a", 1, 0).Times(1)
			err := syncer.AntiEntropy(ctx)
			convey.So(err, convey.ShouldBeNil)
		})
		convey.Convey("general case, sync dynamodb to redis", func() {
			mockRedisRw.EXPECT().ListAPIKeys(any).Return(
				map[string]ddbtoredis.APIKey{
					"1": {"a", 1, 0},
					"3": {"c", 3, 1},
					"2": {"f", 2, 2},
					"5": {"e", 4, 0},
				},
				nil,
			)
			mockDynamodbR.EXPECT().ScanRecords(any).Return(
				[]ddbtoredis.KeyDynamodb{
					{"4", "d", 4, 0},
					{"1", "a", 1, 0},
					{"2", "b", 2, 2},
					{"5", "e", 4, 1},
				},
				nil,
			)
			mockRedisRw.EXPECT().DeleteAPIKey(any, "3").Times(1)
			mockRedisRw.EXPECT().SetAPIKey(any, "4", "d", 4, 0).Times(1)
			mockRedisRw.EXPECT().SetAPIKey(any, "2", "b", 2, 2).Times(1)
			mockRedisRw.EXPECT().SetAPIKey(any, "5", "e", 4, 1).Times(1)
			err := syncer.AntiEntropy(ctx)
			convey.So(err, convey.ShouldBeNil)
		})
	})
}
