package precheck

import (
	"context"
	"testing"

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

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

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

func (suite *CheckSingleTableTestSuite) TearDownTest() {
	suite.ctrl.Finish()
	if suite.checker != nil {
		suite.checker.useCustomFileMapping = false
	}
}

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

	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: true,
			Empty:  false,
		}, nil)

	tableMeta := &TableMeta{
		Name: "table",
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}

	result, err := suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(false, result.UseSchemaFile)
	assert.Equal("table test.table is not empty", result.ErrorMsg)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)

	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: true,
			Empty:  false,
		}, nil)

	tableMeta = &TableMeta{
		Name: "table",
		SchemaFiles: []RouteResult{
			{FilePath: "test_db/test.table-schema1.sql"},
		},
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}

	result, err = suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(false, result.UseSchemaFile)
	assert.Equal("table test.table is not empty", result.ErrorMsg)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)

	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: true,
			Empty:  false,
		}, nil)

	tableMeta = &TableMeta{
		Name: "table",
		SchemaFiles: []RouteResult{
			{FilePath: "test_db/test.table-schema.sql"},
			{FilePath: "test_db/test.table-schema.sql"},
		},
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}

	result, err = suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(false, result.UseSchemaFile)
	assert.Equal("table test.table is not empty", result.ErrorMsg)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)
}

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

	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: false,
			Empty:  true,
		}, nil)

	tableMeta := &TableMeta{
		Name: "table",
		SchemaFiles: []RouteResult{
			{FilePath: "test_db/test.table-schema1.sql"},
			{FilePath: "test_db/test.table-schema2.sql"},
		},
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}

	result, err := suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(true, result.UseSchemaFile)
	assert.Equal("table test.table has duplicated schema files: [test_db/test.table-schema1.sql test_db/test.table-schema2.sql]", result.ErrorMsg)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)

	// Test case no data files
	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: false,
			Empty:  true,
		}, nil)

	tableMeta = &TableMeta{
		Name: "table",
		SchemaFiles: []RouteResult{
			{FilePath: "test_db/test.table-schema1.sql"},
			{FilePath: "test_db/test.table-schema2.sql"},
		},
		DataFiles: []RouteResult{},
	}

	result, err = suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(true, result.UseSchemaFile)
	assert.Equal("table test.table has duplicated schema files: [test_db/test.table-schema1.sql test_db/test.table-schema2.sql]", result.ErrorMsg)
	assert.Equal([]string{}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)
}

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

	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: false,
			Empty:  false,
		}, nil)

	suite.store.EXPECT().
		RetrieveSchemaFile(ctx, "test", "table", "test_db/").
		Return(nil, nil)
	tableMeta := &TableMeta{
		Name: "table",
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}

	result, err := suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, true)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(true, result.UseSchemaFile)
	assert.Equal("table `test`.`table` schema not found", result.ErrorMsg)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)

	// Test case when resp is not truncated
	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: false,
			Empty:  false,
		}, nil)

	result, err = suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(true, result.UseSchemaFile)
	assert.Equal("table `test`.`table` schema not found", result.ErrorMsg)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)
}

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

	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: true,
			Empty:  true,
		}, nil)

	tableMeta := &TableMeta{
		Name: "table",
		SchemaFiles: []RouteResult{
			{FilePath: "test_db/test.table-schema.sql"},
		},
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}

	result, err := suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Empty(result.ErrorMsg)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)
	assert.Equal(false, result.UseSchemaFile)
	assert.Equal("test_db/test.table-schema.sql", result.SchemaFile)

	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: true,
			Empty:  true,
		}, nil)

	tableMeta = &TableMeta{
		Name:        "table",
		SchemaFiles: []RouteResult{},
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}

	result, err = suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Empty(result.ErrorMsg)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)
	assert.Equal(false, result.UseSchemaFile)
	assert.Empty(result.SchemaFile)
}

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

	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: false,
			Empty:  false,
		}, nil)

	tableMeta := &TableMeta{
		Name: "table",
		SchemaFiles: []RouteResult{
			{FilePath: "test_db/test.table-schema.sql"},
		},
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}

	result, err := suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Empty(result.ErrorMsg)
	assert.Equal(true, result.UseSchemaFile)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)
	assert.Equal("test_db/test.table-schema.sql", result.SchemaFile)
}

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

	// Case 1: useCustomFileMapping = true, table exists
	suite.checker.useCustomFileMapping = true
	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: true,
			Empty:  true,
		}, nil)
	tableMeta := &TableMeta{
		Name: "table",
		SchemaFiles: []RouteResult{
			{FilePath: "test_db/test.table-schema.sql"},
		},
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}
	result, err := suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, true)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Empty(result.ErrorMsg)
	assert.Equal(false, result.UseSchemaFile)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)

	// Case 2: useCustomFileMapping = true, table does not exist, schema file exists
	suite.checker.useCustomFileMapping = true
	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: false,
			Empty:  true,
		}, nil)
	tableMeta = &TableMeta{
		Name: "table",
		SchemaFiles: []RouteResult{
			{FilePath: "test_db/test.table-schema.sql"},
		},
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}
	result, err = suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, true)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(false, result.UseSchemaFile)
	assert.Equal("table test.table does not exist", result.ErrorMsg)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)

	// Case 3: useCustomFileMapping = false, table does not exist, schema file exists
	suite.checker.useCustomFileMapping = false
	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: false,
			Empty:  true,
		}, nil)
	tableMeta = &TableMeta{
		Name: "table",
		SchemaFiles: []RouteResult{
			{FilePath: "test_db/test.table-schema.sql"},
		},
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}
	result, err = suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(true, result.UseSchemaFile)
	assert.Empty(result.ErrorMsg) // Should not return 'does not exist' error
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)

	// Case 4: useCustomFileMapping = true, table does not exist, schema file does not exist
	suite.checker.useCustomFileMapping = true
	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: false,
			Empty:  true,
		}, nil)
	tableMeta = &TableMeta{
		Name:        "table",
		SchemaFiles: []RouteResult{},
		DataFiles: []RouteResult{
			{FilePath: "test_db/test.table.sql"},
		},
	}
	result, err = suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, true)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(false, result.UseSchemaFile)
	assert.Equal("table test.table does not exist", result.ErrorMsg)
	assert.Equal([]string{"test_db/test.table.sql"}, result.DataFiles)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)
}

func (suite *CheckSingleTableTestSuite) TestCheckSingleTable_NoDatafiles() {
	assert := require.New(suite.T())
	ctx := context.Background()
	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: false,
			Empty:  false,
		}, nil)
	tableMeta := &TableMeta{
		Name: "table",
		SchemaFiles: []RouteResult{
			{FilePath: "test_db/test.table-schema.sql"},
		},
		DataFiles: []RouteResult{},
	}

	result, err := suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(true, result.UseSchemaFile)
	assert.Empty(result.ErrorMsg)
	assert.Equal([]string{}, result.DataFiles)
	assert.Equal("test_db/test.table-schema.sql", result.SchemaFile)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)

	suite.schemaChecker.EXPECT().
		CheckSchema(ctx, "test", "table").
		Return(&SchemaCheckResult{
			Exists: true,
			Empty:  true,
		}, nil)
	tableMeta = &TableMeta{
		Name: "table",
		SchemaFiles: []RouteResult{
			{FilePath: "test_db/test.table-schema.sql"},
		},
		DataFiles: []RouteResult{},
	}

	result, err = suite.checker.checkSingleTable(ctx, "test", "table", tableMeta, false)
	assert.NoError(err)
	assert.NotNil(result)
	assert.Equal(false, result.UseSchemaFile)
	assert.Empty(result.ErrorMsg)
	assert.Equal([]string{}, result.DataFiles)
	assert.Equal("test_db/test.table-schema.sql", result.SchemaFile)
	assert.Equal(Table{Schema: "test", Name: "table"}, result.Table)
}

func TestCheckSingleTableTestSuite(t *testing.T) {
	suite.Run(t, new(CheckSingleTableTestSuite))
}
