package precheck

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/import/cloud_storage"
	"github.com/tidbcloud/pkgv2/log"
	"go.uber.org/mock/gomock"
	"go.uber.org/zap"
)

type RunMethodTestSuite struct {
	suite.Suite
	ctrl          *gomock.Controller
	store         *cloud_storage.MockCloudStorage
	fileRouter    *MockFileRouter
	schemaChecker *MockSchemaChecker
	logger        *zap.Logger
	checker       *Checker
}

func (suite *RunMethodTestSuite) SetupTest() {
	suite.ctrl = gomock.NewController(suite.T())
	suite.store = cloud_storage.NewMockCloudStorage(suite.ctrl)
	suite.fileRouter = NewMockFileRouter(suite.ctrl)
	suite.schemaChecker = NewMockSchemaChecker(suite.ctrl)
	suite.logger = log.WithContextL(context.Background())
	suite.checker = &Checker{
		store:         suite.store,
		fileRouter:    suite.fileRouter,
		schemaChecker: suite.schemaChecker,
	}
}

func (suite *RunMethodTestSuite) TearDownTest() {
	suite.ctrl.Finish()
}

func (suite *RunMethodTestSuite) TestRun_Success() {
	assert := require.New(suite.T())
	ctx := context.Background()

	// Mock store.ListLimited
	files := []string{
		"test_db/test.table1-schema.sql",
		"test_db/test.table1.sql",
	}
	suite.store.EXPECT().
		ListLimited(ctx, nil).
		Return(files, false, nil)

	// Mock fileRouter.Route
	suite.fileRouter.EXPECT().
		Route("test_db/test.table1-schema.sql").
		Return(&RouteResult{
			Table:    Table{Schema: "test", Name: "table1"},
			Type:     TableSchema,
			FilePath: "test_db/test.table1-schema.sql",
		}, nil)

	suite.fileRouter.EXPECT().
		Route("test_db/test.table1.sql").
		Return(&RouteResult{
			Table:    Table{Schema: "test", Name: "table1"},
			Type:     TypeSQL,
			FilePath: "test_db/test.table1.sql",
		}, nil)

	// Mock schemaChecker.CheckSchema
	suite.schemaChecker.EXPECT().
		CheckSchema(gomock.Any(), "test", "table1").
		Return(&SchemaCheckResult{Exists: false, Empty: true}, nil)

	// Call the method
	result, bizErr := suite.checker.Run(ctx)

	// Verify results
	assert.NoError(bizErr)
	assert.NotNil(result)
	assert.Equal(PrecheckStatusCompleted, result.Status)

	// Verify table result
	assert.ElementsMatch([]TableResult{
		{
			Table:         Table{Schema: "test", Name: "table1"},
			DataFiles:     []string{"test_db/test.table1.sql"},
			ErrorMsg:      "",
			UseSchemaFile: true,
			SchemaFile:    "test_db/test.table1-schema.sql",
		},
	}, result.TableResults)
}

func (suite *RunMethodTestSuite) TestRun_ListFilesError() {
	assert := require.New(suite.T())
	ctx := context.Background()

	// Mock store.ListLimited to return error
	suite.store.EXPECT().
		ListLimited(ctx, nil).
		Return(nil, false, bizerr.New(bizerr.ErrCodeUnknown, "failed to list files"))

	// Call the method
	result, bizErr := suite.checker.Run(ctx)

	// Verify error is returned
	assert.Error(bizErr)
	assert.Equal(bizerr.ErrCodeUnknown, bizErr.RawErrCode())
	assert.Nil(result)
}

func (suite *RunMethodTestSuite) TestRun_BuildDbMapError() {
	assert := require.New(suite.T())
	ctx := context.Background()

	// Mock store.ListLimited
	files := []string{"test_db/test.table1.sql"}
	suite.store.EXPECT().
		ListLimited(ctx, nil).
		Return(files, false, nil)

	// Mock fileRouter.Route to return error
	suite.fileRouter.EXPECT().
		Route("test_db/test.table1.sql").
		Return(nil, errors.New("router error"))

	// Call the method
	result, bizErr := suite.checker.Run(ctx)

	// Verify error is returned
	assert.Error(bizErr)
	assert.Equal(bizerr.ErrCodeUnknown, bizErr.RawErrCode())
	assert.Nil(result)
}

func TestRunMethodTestSuite(t *testing.T) {
	suite.Run(t, new(RunMethodTestSuite))
}

// TestTruncatePaths tests the truncatePaths helper function
func TestTruncatePaths(t *testing.T) {
	assert := require.New(t)
	tests := []struct {
		name     string
		files    []RouteResult
		expected []string
	}{
		{
			name:     "Empty files",
			files:    []RouteResult{},
			expected: []string{},
		},
		{
			name: "Files less than limit",
			files: []RouteResult{
				{FilePath: "file1.sql"},
				{FilePath: "file2.sql"},
				{FilePath: "file3.sql"},
			},
			expected: []string{"file1.sql", "file2.sql", "file3.sql"},
		},
		{
			name: "Files equal to limit",
			files: []RouteResult{
				{FilePath: "file1.sql"},
				{FilePath: "file2.sql"},
				{FilePath: "file3.sql"},
				{FilePath: "file4.sql"},
				{FilePath: "file5.sql"},
			},
			expected: []string{"file1.sql", "file2.sql", "file3.sql", "file4.sql", "file5.sql"},
		},
		{
			name: "Files more than limit",
			files: []RouteResult{
				{FilePath: "file1.sql"},
				{FilePath: "file2.sql"},
				{FilePath: "file3.sql"},
				{FilePath: "file4.sql"},
				{FilePath: "file5.sql"},
				{FilePath: "file6.sql"},
				{FilePath: "file7.sql"},
			},
			expected: []string{"file1.sql", "file2.sql", "file3.sql", "file4.sql", "file5.sql", "..."},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := truncatePaths(tt.files)
			assert.Equal(tt.expected, result)
		})
	}
}
