package main

import (
	"context"

	"github.com/google/uuid"
	"github.com/pingcap/test-infra/caselib/pkg/host"
	"github.com/pingcap/test-infra/caselib/pkg/steps"
	"github.com/pingcap/test-infra/caselib/pkg/utils"
	apiresource "github.com/pingcap/test-infra/common/model/resource"
	_ "github.com/pingcap/test-infra/sdk/resource/impl/k8s"
	"go.uber.org/zap"
)

var (
	srcTidb               = &apiresource.ResourceHandle{}
	dstTidb               = &apiresource.ResourceHandle{}
	brHost                = &apiresource.ResourceHandle{}
	mcHost                = &apiresource.ResourceHandle{}
	lightningRes          = &apiresource.ResourceHandle{}
	lightningOpts         = &steps.LightningImportOpts{}
	backupOpts            = &host.BackupDbOpts{}
	mcRmOpts              = &host.McRmOpts{}
	uniq                  = uuid.New().String()
	globalVariableOpts    = &steps.TiDBVariableList
	adminCheck            string
	dropDatabases         string
	compatibleVersion     string
	expectErrorCompToComp bool
	expectErrorCompToCur  bool
	expectErrorCurToComp  bool
)

func backupAndRestore(srcTidb, dstTidb *apiresource.ResourceHandle, expectError *bool) func(b *steps.Builder) {
	return func(b *steps.Builder) {
		b.DropDatabasesIfExists(srcTidb, &dropDatabases).
			ConfigGlobalVariable(globalVariableOpts, srcTidb).
			// import test data for later backup & restore testing
			LightningImport(lightningOpts, lightningRes, srcTidb).
			CheckDatabasesCrc32(&lightningOpts.Crc32Checksum, &dropDatabases, srcTidb).
			BrBackupDb(brHost, srcTidb, backupOpts).
			DropDatabasesIfExists(dstTidb, &dropDatabases).
			ConfigGlobalVariable(globalVariableOpts, dstTidb).
			ExpectError(expectError, steps.NewBrRestoreDb(brHost, dstTidb, backupOpts)).
			CheckDatabasesCrc32(&backupOpts.Crc32Checksum, &dropDatabases, dstTidb).
			AdminCheck(dstTidb, &adminCheck).
			McRemove(mcRmOpts, mcHost)
	}
}

// BrCompatibility test current br version (e.g. 5.4) compatibility with current tidb version (e.g. 5.4) and compatible tidb version (e.g. 4.0, 5.1 - 5.4)
func BrCompatibility() steps.Step {
	return steps.
		NewBuilder().
		LoadEnv().
		LoadResourceFromEnv(utils.ResourcePrefix+"src_tidb", srcTidb).
		LoadResourceFromEnv(utils.ResourcePrefix+"dst_tidb", dstTidb).
		LoadResourceFromEnv(utils.ParamPrefix+"import_basic_opts", lightningOpts).
		LoadResourceFromEnv(utils.ResourcePrefix+"lightning", lightningRes).
		LoadResourceFromEnv(utils.ResourcePrefix+"br", brHost).
		LoadResourceFromEnv(utils.ResourcePrefix+"mc", mcHost).
		LoadResourceFromEnv(utils.ParamPrefix+"br_opts", backupOpts).
		LoadResourceFromEnv(utils.ParamPrefix+"mc_rm_opts", mcRmOpts).
		LoadResourceFromEnv(utils.ParamPrefix+"global_variable", &globalVariableOpts).
		LoadParamFromEnv(utils.ParamPrefix+"admin_check", &adminCheck).
		LoadParamFromEnv(utils.ParamPrefix+"drop_databases", &dropDatabases).
		// parameter expectErrorCompToComp defines whether restore from compatible version to compatibility version(e.g. 4.0 -> 4.0)  should fail
		//   it should not fail.
		LoadResourceFromEnv(utils.ParamPrefix+"expect_error_restore_from_comp_to_comp", &expectErrorCompToComp).
		// parameter expectErrorCompToComp defines whether restore from compatible version to current version(e.g. 4.0 -> 5.4)  should fail
		//   it should not fail.
		LoadResourceFromEnv(utils.ParamPrefix+"expect_error_restore_from_comp_to_cur", &expectErrorCompToCur).
		// parameter expectErrorCompToComp defines whether restore from current version to compatible version(e.g. 5.4 -> 4.0)  should fail
		//   possibly backup of current version (e.g. 5.4) can't be restored to compatible version (e.g. 4.0)
		//   e.g. backup of table with primary key of the non-integer clustered index can't be restored to tidb 4.x (which doesn't support clustered index)
		LoadResourceFromEnv(utils.ParamPrefix+"expect_error_restore_from_cur_to_comp", &expectErrorCurToComp).
		// compatibleVersion is the version of tidb that current br version is compatible with
		//   e.g. br 5.4 compatible with tidb 4.0, 5.1 - 5.4
		LoadParamFromEnv(utils.ParamPrefix+"compatibleVersion", &compatibleVersion).
		NewUniqPath(&backupOpts.Storage, uniq).
		NewUniqPath(&mcRmOpts.Path, uniq).
		// Upgrade srcTidb to target compatible tidb version, e.g. 4.0
		UpgradeTiDBAllComponent(srcTidb, &compatibleVersion, 0).
		// dstTiDB is current version under testing (e.g. 5.4), srcTiDB is compatible tidb version. (e.g. 4.0)
		// Verify br can:
		//   1): backup from compatible tidb version, and restore to current version under testing
		Include(backupAndRestore(srcTidb, srcTidb, &expectErrorCompToComp)).
		//   2): backup from compatible tidb version, and restore to compatible tidb version
		Include(backupAndRestore(srcTidb, dstTidb, &expectErrorCompToCur)).
		//   3): backup from current version under testing, and compatible tidb version
		Include(backupAndRestore(dstTidb, srcTidb, &expectErrorCurToComp)).
		Build()
}

func main() {
	_, logger := utils.GetCtxAndLogger()
	ctx := steps.New(context.Background())
	if err := BrCompatibility().Execute(ctx); err != nil {
		logger.Fatal("Br Compatibility failed", zap.Error(err))
	}
	logger.Info("Br Compatibility passed")
}
