package cron

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

	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	tntent "github.com/tidbcloud/devops/entity/tenant"
	"github.com/tidbcloud/devops/pkg/infra/clients/accountcli"
	"github.com/tidbcloud/devops/pkg/infra/utils/test"
	"github.com/tidbcloud/devops/pkg/service/tenant/cron/enricher"
	"github.com/tidbcloud/devops/pkg/service/tenant/repo"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"go.uber.org/mock/gomock"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func TestSyncTenantsCron(t *testing.T) {
	RegisterFailHandler(Fail)
	RunSpecs(t, "Sync Tenants Cron Suite", test.ReporterConfig())
}

var _ = Describe("[SyncTenantsCron]", func() {
	var fw = test.Framework()

	var (
		ctx        context.Context
		tenantRepo *repo.MockTenantRepo
		accountCli *accountcli.MockClient
		enricher1  *enricher.MockEnricher
		cron       SyncTenantsCron
	)

	BeforeEach(func() {
		ctx = context.Background()
		tenantRepo = repo.NewMockTenantRepo(fw.Ctrl)
		accountCli = accountcli.NewMockClient(fw.Ctrl)
		enricher1 = enricher.NewMockEnricher(fw.Ctrl)
		cron = NewSyncTenantsCron(tenantRepo, accountCli, []enricher.Enricher{enricher1})
	})

	Describe("SyncTenants", func() {
		It("success", func() {
			now := time.Now()
			accountCli.EXPECT().ListTenants(gomock.Any(), &account.OrgFilter{}).Return([]*account.Org{
				{
					Id:        1,
					Name:      "tenant-1",
					Status:    "active",
					Email:     "tenant-1@pingcap.com",
					CreatedAt: timestamppb.New(now),
					Country:   "us",
					TimeZone:  "UTC",
					Kind:      "external",
				},
			}, nil)
			enricher1.EXPECT().Enrich(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, tenants []*tntent.Tenant) error {
				Expect(tenants).To(HaveLen(1))
				Expect(tenants[0].CountryRegion).To(Equal(tntent.TenantRegionNA))
				return nil
			})
			tenantRepo.EXPECT().BatchSave(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, tenants []*tntent.Tenant) error {
				Expect(tenants).To(HaveLen(1))
				Expect(tenants[0].Id).To(Equal(uint64(1)))
				Expect(tenants[0].Name).To(Equal("tenant-1"))
				Expect(tenants[0].Status).To(Equal(tntent.TenantStatus("active")))
				Expect(tenants[0].Kind).To(Equal(tntent.TenantKind("external")))
				Expect(tenants[0].CreatedAt).ToNot(BeNil())
				Expect(tenants[0].CreatedAt.Unix()).To(Equal(now.Unix()))
				return nil
			})

			err := cron.SyncTenants(ctx)
			Expect(err).To(BeNil())
		})

		It("batch save by 100", func() {
			now := time.Now()
			orgs := make([]*account.Org, 0, 201)
			for i := 1; i <= 201; i++ {
				orgs = append(orgs, &account.Org{
					Id:        uint64(i),
					Name:      fmt.Sprintf("tenant-%d", i),
					Status:    "active",
					Email:     fmt.Sprintf("tenant-%d@pingcap.com", i),
					CreatedAt: timestamppb.New(now),
					Country:   "de",
					TimeZone:  "UTC",
					Kind:      "external",
				})
			}
			accountCli.EXPECT().ListTenants(gomock.Any(), &account.OrgFilter{}).Return(orgs, nil)
			enricher1.EXPECT().Enrich(gomock.Any(), gomock.Any()).Return(nil)
			batchSizes := make([]int, 0, 3)
			tenantRepo.EXPECT().BatchSave(gomock.Any(), gomock.Any()).Times(3).DoAndReturn(func(_ context.Context, tenants []*tntent.Tenant) error {
				batchSizes = append(batchSizes, len(tenants))
				return nil
			})

			err := cron.SyncTenants(ctx)
			Expect(err).To(BeNil())
			Expect(batchSizes).To(Equal([]int{100, 100, 1}))
		})

		It("return nil when lock is held", func() {
			impl := cron.(*syncTenantsCron)
			impl.mutex.Lock()
			defer impl.mutex.Unlock()

			err := cron.SyncTenants(ctx)
			Expect(err).To(BeNil())
		})

		It("return error when list tenants failed", func() {
			accountCli.EXPECT().ListTenants(gomock.Any(), &account.OrgFilter{}).Return(nil, errors.New("boom"))

			err := cron.SyncTenants(ctx)
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("sync tenants failed"))
			Expect(err.Error()).To(ContainSubstring("list tenants from account failed"))
		})

		It("return error when enrich failed", func() {
			now := time.Now()
			accountCli.EXPECT().ListTenants(gomock.Any(), &account.OrgFilter{}).Return([]*account.Org{
				{
					Id:        1,
					Name:      "tenant-1",
					Status:    "active",
					Email:     "tenant-1@pingcap.com",
					CreatedAt: timestamppb.New(now),
					Country:   "us",
					TimeZone:  "UTC",
					Kind:      "external",
				},
			}, nil)
			enricher1.EXPECT().Enrich(gomock.Any(), gomock.Any()).Return(errors.New("enrich failed"))

			err := cron.SyncTenants(ctx)
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("sync tenants failed"))
			Expect(err.Error()).To(ContainSubstring("enrich tenants failed"))
		})

		It("return error when batch save failed", func() {
			now := time.Now()
			accountCli.EXPECT().ListTenants(gomock.Any(), &account.OrgFilter{}).Return([]*account.Org{
				{
					Id:        1,
					Name:      "tenant-1",
					Status:    "active",
					Email:     "tenant-1@pingcap.com",
					CreatedAt: timestamppb.New(now),
					Country:   "us",
					TimeZone:  "UTC",
					Kind:      "external",
				},
			}, nil)
			enricher1.EXPECT().Enrich(gomock.Any(), gomock.Any()).Return(nil)
			tenantRepo.EXPECT().BatchSave(gomock.Any(), gomock.Any()).Return(errors.New("save failed"))

			err := cron.SyncTenants(ctx)
			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("sync tenants failed"))
			Expect(err.Error()).To(ContainSubstring("batch save tenants failed"))
		})
	})
})
