package importrepo_test

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	"github.com/tidbcloud/dataflow-service/app/repo"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	importrepo "github.com/tidbcloud/dataflow-service/app/repo/import"
	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm"
)

type importImplTestSuite struct {
	suite.Suite
	db      *gorm.DB
	impRepo importrepo.ImportRepo
}

func (s *importImplTestSuite) SetupSuite() {
	entity.NewDataflowEventHookerManager()
	testDB, err := repo.GetFakeDB()
	s.Require().NoError(err)
	s.db = testDB

	s.Require().NoError(prepareData(testDB))

	provider := db.NewProvider(testDB)

	s.impRepo = importrepo.NewImportRepoImpl(provider)
}

//nolint:funlen
func prepareData(db *gorm.DB) error {
	baseTS, _ := time.Parse(time.RFC3339, "2023-01-01T16:00:00+08:00")
	baseTS2, _ := time.Parse(time.RFC3339, "2023-01-01T16:00:10+08:00")
	imps := []entity.Import{
		{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID:        1,
					CreatedAt: baseTS,
				},
			},
			ClusterID: 123,
			ProjectID: 9001,
			TenantID:  90001,
			Status:    entity.ImportStatusCompleted,
		},
		{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID:        2,
					CreatedAt: baseTS.AddDate(0, 0, 1),
				},
			},
			ClusterID: 123,
			ProjectID: 9001,
			TenantID:  90001,
			Status:    entity.ImportStatusFailed,
			Message:   "some error message",
		},
		{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID:        3,
					CreatedAt: baseTS.AddDate(0, 0, 2),
				},
			},
			ClusterID: 123,
			ProjectID: 9001,
			TenantID:  90001,
			Status:    entity.ImportStatusCanceled,
		},
		{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID:        4,
					CreatedAt: baseTS2,
				},
			},
			ClusterID: 456,
			ProjectID: 9002,
			TenantID:  90002,
			Status:    entity.ImportStatusCompleted,
		},
		{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID:        5,
					CreatedAt: baseTS2.AddDate(0, 0, 1),
				},
			},
			ClusterID: 456,
			ProjectID: 9002,
			TenantID:  90002,
			Status:    entity.ImportStatusFailed,
			Message:   "some error message",
		},
	}
	result := db.Create(&imps)
	return result.Error
}

func TestImportImplTestSuite(t *testing.T) {
	suite.Run(t, new(importImplTestSuite))
}

//nolint:funlen
func (s *importImplTestSuite) TestListImports() {
	testCases := []struct {
		CaseName        string
		ImportFilterOps []importrepo.ImportFilterOp
		ExpectIDs       []int
	}{
		{
			CaseName: "filter by cluster id order by created at desc",
			ImportFilterOps: []importrepo.ImportFilterOp{
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.ClusterIDEq(123).OrderDescByCreatedAt()
				},
			},
			ExpectIDs: []int{3, 2, 1},
		},
		{
			CaseName: "filter by cluster id order by created at desc with offset and limit",
			ImportFilterOps: []importrepo.ImportFilterOp{
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.ClusterIDEq(123).OrderDescByCreatedAt()
				},
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.Offset(1).Limit(2)
				},
			},
			ExpectIDs: []int{2, 1},
		},
		{
			CaseName: "filter by status order by created at desc",
			ImportFilterOps: []importrepo.ImportFilterOp{
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.StatusIn(
						entity.ImportStatusCompleted,
						entity.ImportStatusFailed,
					).OrderDescByCreatedAt()
				},
			},
			ExpectIDs: []int{5, 2, 4, 1},
		},
		{
			CaseName: "filter by status order by created at desc with offset and limit",
			ImportFilterOps: []importrepo.ImportFilterOp{
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.StatusIn(
						entity.ImportStatusCompleted,
						entity.ImportStatusFailed,
					).OrderDescByCreatedAt()
				},
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.Offset(1).Limit(2)
				},
			},
			ExpectIDs: []int{2, 4},
		},
		{
			CaseName: "filter by cluster and status order by created at desc",
			ImportFilterOps: []importrepo.ImportFilterOp{
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.StatusIn(
						entity.ImportStatusCompleted,
						entity.ImportStatusFailed,
					).OrderDescByCreatedAt()
				},
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.ClusterIDEq(123)
				},
			},
			ExpectIDs: []int{2, 1},
		},
		{
			CaseName: "filter by not exist",
			ImportFilterOps: []importrepo.ImportFilterOp{
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.ClusterIDEq(789)
				},
			},
			ExpectIDs: []int{},
		},
	}
	for _, tc := range testCases {
		s.T().Run(tc.CaseName, func(t *testing.T) {
			imps, bizErr := s.impRepo.List(context.Background(), tc.ImportFilterOps...)
			require.NoError(t, bizErr)
			t.Logf("imports: %+v", imps)
			require.Len(t, imps, len(tc.ExpectIDs))
			impIDs := make([]int, len(imps))
			for i, imp := range imps {
				impIDs[i] = int(imp.ID)
			}
			require.Equal(t, tc.ExpectIDs, impIDs)
		})
	}
}

func (s *importImplTestSuite) TestCountImports() {
	testCases := []struct {
		CaseName        string
		ImportFilterOps []importrepo.ImportFilterOp
		ExpectCount     int
	}{
		{
			CaseName: "filter by cluster id",
			ImportFilterOps: []importrepo.ImportFilterOp{
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.ClusterIDEq(123)
				},
			},
			ExpectCount: 3,
		},
		{
			CaseName: "filter by status",
			ImportFilterOps: []importrepo.ImportFilterOp{
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.StatusIn(
						entity.ImportStatusCompleted,
						entity.ImportStatusFailed,
					)
				},
			},
			ExpectCount: 4,
		},
		{
			CaseName: "filter by cluster and status",
			ImportFilterOps: []importrepo.ImportFilterOp{
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.StatusIn(
						entity.ImportStatusCompleted,
						entity.ImportStatusFailed,
					)
				},
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.ClusterIDEq(123)
				},
			},
			ExpectCount: 2,
		},
		{
			CaseName: "filter by not exist",
			ImportFilterOps: []importrepo.ImportFilterOp{
				func(qs entity.ImportQuerySet) entity.ImportQuerySet {
					return qs.ClusterIDEq(789)
				},
			},
			ExpectCount: 0,
		},
	}
	for _, tc := range testCases {
		s.T().Run(tc.CaseName, func(t *testing.T) {
			cnt, bizErr := s.impRepo.Count(context.Background(), tc.ImportFilterOps...)
			require.NoError(t, bizErr)
			require.Equal(t, tc.ExpectCount, int(cnt))
		})
	}
}

func (s *importImplTestSuite) TestLoadImport() {
	imp, bizErr := s.impRepo.Load(context.Background(), 5)
	s.Require().NoError(bizErr)
	s.T().Logf("import: %+v", imp)
	s.Require().Equal(5, int(imp.ID))
	s.Require().Equal(456, int(imp.ClusterID))

	_, bizErr = s.impRepo.Load(context.Background(), 999)
	s.Require().Error(bizErr)
	s.T().Logf("bizErr: %+v", bizErr)
	s.Require().Contains(bizErr.Error(), "loading import entity has errors: record not found")
}
