package precheck

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	"github.com/tidbcloud/pkgv2/log"
	"go.uber.org/zap"
)

type BuildDbMetaMapTestSuite struct {
	suite.Suite
	defaultCfg []*FileRouteRule
	fileRouter FileRouter
	logger     *zap.Logger
}

func (suite *BuildDbMetaMapTestSuite) SetupTest() {
	assert := require.New(suite.T())
	ctx := context.Background()
	suite.defaultCfg = []*FileRouteRule{
		{Pattern: `(?i)^(?:[^/]*/)*([^/.]+)-schema-create\.sql(?:\.(\w+))?$`, Schema: "$1", Type: SchemaSchema},
		{Pattern: `(?i)^(?:[^/]*/)*([^/.]+)\.(.*?)-schema\.sql(?:\.(\w+))?$`, Schema: "$1", Table: "$2", Type: TableSchema},
		{Pattern: `(?i)^(?:[^/]*/)*([^/.]+)\.([^/.]+?)(?:(?:\.\d+)|(?:\.?(?:_\d+){3}))?\.sql(?:\.(\w+))?$`, Schema: "$1", Table: "$2", Type: TypeSQL},
	}
	var err error
	suite.fileRouter, err = NewFileRouter(ctx, suite.defaultCfg)
	assert.NoError(err)
	suite.logger = log.WithContextL(context.Background())
}

func (suite *BuildDbMetaMapTestSuite) TestBuildDbMetaMap_NoMatch() {
	assert := require.New(suite.T())
	checker := &Checker{fileRouter: suite.fileRouter}
	files := []string{"test_db/test_table.sql", "test_db/test_table_schema.sql", "test_db/test_table.schema.json"}

	got, bizErr := checker.buildDbMetaMap(context.Background(), files)
	assert.NoError(bizErr)
	assert.EqualValues(map[string]*DbMeta{}, got)
}

func (suite *BuildDbMetaMapTestSuite) TestBuildDbMetaMap_OnlyTableSchema() {
	assert := require.New(suite.T())
	checker := &Checker{fileRouter: suite.fileRouter}
	files := []string{"test_db/test.table-schema.sql"}

	got, err := checker.buildDbMetaMap(context.Background(), files)
	assert.NoError(err)

	expected := map[string]*DbMeta{
		"test": {
			Name: "test",
			TableMap: map[string]*TableMeta{
				"table": {
					Name:        "table",
					SchemaFiles: []RouteResult{{Table: Table{Schema: "test", Name: "table"}, Type: TableSchema, FilePath: "test_db/test.table-schema.sql"}},
				},
			},
		},
	}

	assert.EqualValues(expected, got)
}

func (suite *BuildDbMetaMapTestSuite) TestBuildDbMetaMap_OnlyDBSchema() {
	assert := require.New(suite.T())
	checker := &Checker{fileRouter: suite.fileRouter}
	files := []string{"test_db/test-schema-create.sql"}

	got, err := checker.buildDbMetaMap(context.Background(), files)
	assert.NoError(err)

	expected := map[string]*DbMeta{
		"test": {
			Name: "test",
			SchemaFile: &RouteResult{
				Table:    Table{Schema: "test"},
				Type:     SchemaSchema,
				FilePath: "test_db/test-schema-create.sql",
			},
			TableMap: map[string]*TableMeta{},
		},
	}

	assert.EqualValues(expected, got)
}

func (suite *BuildDbMetaMapTestSuite) TestBuildDbMetaMap_OnlySQLFile() {
	assert := require.New(suite.T())
	checker := &Checker{fileRouter: suite.fileRouter}
	files := []string{"test_db/test.table.sql"}

	got, err := checker.buildDbMetaMap(context.Background(), files)
	assert.NoError(err)

	expected := map[string]*DbMeta{
		"test": {
			Name: "test",
			TableMap: map[string]*TableMeta{
				"table": {
					Name: "table",
					DataFiles: []RouteResult{{
						Table:    Table{Schema: "test", Name: "table"},
						Type:     TypeSQL,
						FilePath: "test_db/test.table.sql",
					}},
				},
			},
		},
	}

	assert.EqualValues(expected, got)
}

func (suite *BuildDbMetaMapTestSuite) TestBuildDbMetaMap_MultipleFiles() {
	assert := require.New(suite.T())
	checker := &Checker{fileRouter: suite.fileRouter}
	files := []string{
		"test_db/test-schema-create.sql",
		"test_db/test.table1-schema.sql",
		"test_db/test.table2-schema.sql",
		"test_db/test.table1.sql",
		"test_db/test.table2.sql",
	}

	got, err := checker.buildDbMetaMap(context.Background(), files)
	assert.NoError(err)

	expected := map[string]*DbMeta{
		"test": {
			Name: "test",
			SchemaFile: &RouteResult{
				Table:    Table{Schema: "test"},
				Type:     SchemaSchema,
				FilePath: "test_db/test-schema-create.sql",
			},
			TableMap: map[string]*TableMeta{
				"table1": {
					Name: "table1",
					SchemaFiles: []RouteResult{{
						Table:    Table{Schema: "test", Name: "table1"},
						Type:     TableSchema,
						FilePath: "test_db/test.table1-schema.sql",
					}},
					DataFiles: []RouteResult{{
						Table:    Table{Schema: "test", Name: "table1"},
						Type:     TypeSQL,
						FilePath: "test_db/test.table1.sql",
					}},
				},
				"table2": {
					Name: "table2",
					SchemaFiles: []RouteResult{{
						Table:    Table{Schema: "test", Name: "table2"},
						Type:     TableSchema,
						FilePath: "test_db/test.table2-schema.sql",
					}},
					DataFiles: []RouteResult{{
						Table:    Table{Schema: "test", Name: "table2"},
						Type:     TypeSQL,
						FilePath: "test_db/test.table2.sql",
					}},
				},
			},
		},
	}

	assert.EqualValues(expected, got)
}

func (suite *BuildDbMetaMapTestSuite) TestBuildDbMetaMap_CaseSensitivity() {
	assert := require.New(suite.T())
	checker := &Checker{fileRouter: suite.fileRouter}
	files := []string{
		"test_db/TEST-schema-create.sql",
		"test_db/Test.TABLE-schema.sql",
	}

	got, err := checker.buildDbMetaMap(context.Background(), files)
	assert.NoError(err)

	expected := map[string]*DbMeta{
		"test": {
			Name: "test",
			SchemaFile: &RouteResult{
				Table:    Table{Schema: "test"},
				Type:     SchemaSchema,
				FilePath: "test_db/TEST-schema-create.sql",
			},
			TableMap: map[string]*TableMeta{
				"table": {
					Name: "table",
					SchemaFiles: []RouteResult{{
						Table:    Table{Schema: "test", Name: "table"},
						Type:     TableSchema,
						FilePath: "test_db/Test.TABLE-schema.sql",
					}},
				},
			},
		},
	}

	assert.EqualValues(expected, got)
}

func TestBuildDbMetaMap(t *testing.T) {
	suite.Run(t, new(BuildDbMetaMapTestSuite))
}
