package importmodel_test

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	"github.com/tidbcloud/dataflow-service/app/models/domain"
	importmodel "github.com/tidbcloud/dataflow-service/app/models/import"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	mockrepo "github.com/tidbcloud/dataflow-service/app/repo/mock"
)

type importImplTestSuite struct {
	suite.Suite
}

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

func toDomainImps(impEnts []entity.Import) []*domain.ImportTask {
	ret := []*domain.ImportTask{}
	for _, impEnt := range impEnts {
		ret = append(ret, &domain.ImportTask{
			Import: impEnt,
		})
	}
	return ret
}

//nolint:funlen
func (s *importImplTestSuite) TestListImports() {
	ctx := context.Background()
	mockImpEnts := []entity.Import{
		{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID:        1,
					CreatedAt: time.Now(),
				},
			},
			ClusterID: 123,
			ProjectID: 9001,
			TenantID:  90001,
			Status:    entity.ImportStatusCompleted,
		},
		{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID:        2,
					CreatedAt: time.Now(),
				},
			},
			ClusterID: 123,
			ProjectID: 9001,
			TenantID:  90001,
			Status:    entity.ImportStatusFailed,
			Message:   "some error message",
		},
		{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID:        3,
					CreatedAt: time.Now(),
				},
			},
			ClusterID: 123,
			ProjectID: 9001,
			TenantID:  90001,
			Status:    entity.ImportStatusCanceled,
		},
		{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID:        4,
					CreatedAt: time.Now(),
				},
			},
			ClusterID: 456,
			ProjectID: 9001,
			TenantID:  90001,
			Status:    entity.ImportStatusCompleted,
		},
		{
			Updatable: entity.Updatable{
				Base: entity.Base{
					ID:        5,
					CreatedAt: time.Now(),
				},
			},
			ClusterID: 456,
			ProjectID: 9002,
			TenantID:  90002,
			Status:    entity.ImportStatusFailed,
		},
	}
	type testCase struct {
		CaseName   string
		Req        *domain.ListImportsReq
		MockLogic  func(*mockrepo.ImportRepo)
		ExpectResp *domain.ListImportsResp
	}
	testCases := []testCase{
		func() testCase {
			mockListResult := []entity.Import{
				mockImpEnts[0],
				mockImpEnts[1],
				mockImpEnts[2],
			}
			return testCase{
				CaseName: "by cluster id",
				Req: &domain.ListImportsReq{
					ClusterID: 123,
				},
				MockLogic: func(m *mockrepo.ImportRepo) {
					m.On("List", ctx,
						mock.AnythingOfType("importrepo.ImportFilterOp"),
						mock.AnythingOfType("importrepo.ImportFilterOp"),
					).Return(mockListResult, nil)
				},
				ExpectResp: &domain.ListImportsResp{
					Total:   uint32(len(mockListResult)),
					Imports: toDomainImps(mockListResult),
				},
			}
		}(),
		func() testCase {
			mockListResult := []entity.Import{
				mockImpEnts[0],
				mockImpEnts[1],
				mockImpEnts[2],
			}
			mockCountResult := 20
			return testCase{
				CaseName: "by cluster id with pager",
				Req: &domain.ListImportsReq{
					ClusterID: 123,
					Page:      1,
					PerPage:   10,
				},
				MockLogic: func(m *mockrepo.ImportRepo) {
					m.On("List", ctx,
						mock.AnythingOfType("importrepo.ImportFilterOp"),
						mock.AnythingOfType("importrepo.ImportFilterOp"),
						mock.AnythingOfType("importrepo.ImportFilterOp"),
					).Return(mockListResult, nil)
					m.On("Count", ctx,
						mock.AnythingOfType("importrepo.ImportFilterOp"),
					).Return(int64(mockCountResult), nil)
				},
				ExpectResp: &domain.ListImportsResp{
					Total:   uint32(mockCountResult),
					Imports: toDomainImps(mockListResult),
				},
			}
		}(),
		func() testCase {
			mockListResult := []entity.Import{
				mockImpEnts[0],
				mockImpEnts[1],
				mockImpEnts[3],
				mockImpEnts[4],
			}
			return testCase{
				CaseName: "by completed and failed statuses",
				Req: &domain.ListImportsReq{
					Statuses: []entity.ImportStatus{entity.ImportStatusCompleted, entity.ImportStatusFailed},
				},
				MockLogic: func(m *mockrepo.ImportRepo) {
					m.On("List", ctx,
						mock.AnythingOfType("importrepo.ImportFilterOp"),
						mock.AnythingOfType("importrepo.ImportFilterOp"),
					).Return(mockListResult, nil)
				},
				ExpectResp: &domain.ListImportsResp{
					Total:   uint32(len(mockListResult)),
					Imports: toDomainImps(mockListResult),
				},
			}
		}(),
		func() testCase {
			mockListResult := []entity.Import{
				mockImpEnts[0],
				mockImpEnts[1],
			}
			mockCountResult := 10
			return testCase{
				CaseName: "by cluster id and completed and failed statuses with pager",
				Req: &domain.ListImportsReq{
					ClusterID: 123,
					Statuses:  []entity.ImportStatus{entity.ImportStatusCompleted, entity.ImportStatusFailed},
					Page:      1,
					PerPage:   10,
				},
				MockLogic: func(m *mockrepo.ImportRepo) {
					m.On("List", ctx,
						mock.AnythingOfType("importrepo.ImportFilterOp"),
						mock.AnythingOfType("importrepo.ImportFilterOp"),
						mock.AnythingOfType("importrepo.ImportFilterOp"),
						mock.AnythingOfType("importrepo.ImportFilterOp"),
					).Return(mockListResult, nil)
					m.On("Count", ctx,
						mock.AnythingOfType("importrepo.ImportFilterOp"),
						mock.AnythingOfType("importrepo.ImportFilterOp"),
					).Return(int64(mockCountResult), nil)
				},
				ExpectResp: &domain.ListImportsResp{
					Total:   uint32(mockCountResult),
					Imports: toDomainImps(mockListResult),
				},
			}
		}(),
		func() testCase {
			mockCountResult := 99
			return testCase{
				CaseName: "list all with pager",
				Req: &domain.ListImportsReq{
					Page:    1,
					PerPage: 10,
				},
				MockLogic: func(m *mockrepo.ImportRepo) {
					m.On("List", ctx,
						mock.AnythingOfType("importrepo.ImportFilterOp"),
						mock.AnythingOfType("importrepo.ImportFilterOp"),
					).Return(mockImpEnts, nil)
					m.On("Count", ctx).Return(int64(mockCountResult), nil)
				},
				ExpectResp: &domain.ListImportsResp{
					Total:   uint32(mockCountResult),
					Imports: toDomainImps(mockImpEnts),
				},
			}
		}(),
		{
			CaseName: "list all",
			Req:      &domain.ListImportsReq{},
			MockLogic: func(m *mockrepo.ImportRepo) {
				m.On("List", ctx,
					mock.AnythingOfType("importrepo.ImportFilterOp"),
				).Return(mockImpEnts, nil)
			},
			ExpectResp: &domain.ListImportsResp{
				Total:   uint32(len(mockImpEnts)),
				Imports: toDomainImps(mockImpEnts),
			},
		},
	}
	for _, tc := range testCases {
		s.T().Run(tc.CaseName, func(t *testing.T) {
			mockImpRepo := mockrepo.NewImportRepo(t)
			tc.MockLogic(mockImpRepo)
			impModel := importmodel.NewImportModelImpl(mockImpRepo)
			resp, bizErr := impModel.List(ctx, tc.Req)
			require.NoError(t, bizErr)
			t.Logf("resp: %+v", resp)
			require.Equal(t, tc.ExpectResp, resp)
		})
	}
}
