package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"strings"
	"time"

	"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{}
	fullBackupOpts     = &host.BackupFullOpts{}
	mcRmOpts           = &host.McRmOpts{}
	globalVariableOpts = steps.TiDBVariableList
	logBackupOpts      = &host.BackupLogOpts{}
	logRestoreOpts     = &host.RestorePointOpts{}
	uniq               = uuid.New().String()
	uniqFull           = fmt.Sprintf("%s/Full", uniq)
	uniqLog            = fmt.Sprintf("%s/Log", uniq)
	sql0               = new(string)
	sql1               = new(string)
	tsNow              string
	tsHumanReadable    string
	emptyStr           string
)

func CleanUp() steps.Step {
	return steps.
		NewBuilder().
		LoadEnv().
		LoadResourceFromEnv(utils.ResourcePrefix+"upstream", srcTidb).
		LoadResourceFromEnv(utils.ResourcePrefix+"downstream", dstTidb).
		LoadResourceFromEnv(utils.ResourcePrefix+"br", brHost).
		LoadResourceFromEnv(utils.ParamPrefix+"br_log_opts", logBackupOpts).
		IgnoreError(steps.NewStopBrLogTask(brHost, srcTidb, logBackupOpts)).
		Build()
}

func readSql(filename string, sql *string) {
	file, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Println("open file fail", err)
	}
	*sql = strings.ReplaceAll(string(file), ";", "|")
	fmt.Printf("read sql from %s \n success!", filename)
}

func BrPiTR() steps.Step {
	return steps.
		NewBuilder().
		LoadEnv().
		LoadResourceFromEnv(utils.ResourcePrefix+"upstream", srcTidb).
		LoadResourceFromEnv(utils.ResourcePrefix+"downstream", dstTidb).
		LoadResourceFromEnv(utils.ResourcePrefix+"br", brHost).
		LoadResourceFromEnv(utils.ResourcePrefix+"mc", mcHost).
		LoadResourceFromEnv(utils.ParamPrefix+"global_variable", &globalVariableOpts).
		LoadResourceFromEnv(utils.ParamPrefix+"mc_rm_opts", mcRmOpts).
		LoadResourceFromEnv(utils.ParamPrefix+"br_log_opts", logBackupOpts).
		LoadResourceFromEnv(utils.ParamPrefix+"br_opts", fullBackupOpts).
		LoadResourceFromEnv(utils.ParamPrefix+"br_log_restore_opts", logRestoreOpts).
		NewUniqPath(&fullBackupOpts.Storage, uniqFull).
		NewUniqPath(&logBackupOpts.Storage, uniqLog).
		NewUniqPath(&logRestoreOpts.Storage, uniqLog).
		UpdateStructFieldByPointer(logRestoreOpts, "FullBackupStorage", &fullBackupOpts.Storage).
		NewUniqPath(&mcRmOpts.Path, uniq).
		DropUserDatabases(srcTidb).
		DropUserDatabases(dstTidb).
		ConfigGlobalVariable(&globalVariableOpts, srcTidb).
		ConfigGlobalVariable(&globalVariableOpts, dstTidb).
		// log backup
		CreateBrLogTask(brHost, srcTidb, logBackupOpts).
		// query log status
		QueryBrLogStatus(brHost, srcTidb, logBackupOpts).
		BrBackupFull(brHost, srcTidb, fullBackupOpts).
		ExecuteSqlStatements(srcTidb, sql0).
		Sleep(10*time.Second).
		GetTsNow(srcTidb, &tsNow, &tsHumanReadable).
		WaitLogBackupCheckpoint(brHost, srcTidb, logBackupOpts, &tsHumanReadable, 10*time.Minute).
		UpdateStructFieldByPointer(logRestoreOpts, "RestoredTs", &tsNow).
		BrRestorePoint(brHost, dstTidb, logRestoreOpts).
		CheckUserDatabasesConsistency(srcTidb, dstTidb).
		UpdateStructFieldByPointer(logRestoreOpts, "StartTs", &tsNow).
		ExecuteSqlStatements(srcTidb, sql1).
		Sleep(10*time.Second).
		GetTsNow(srcTidb, &tsNow, &tsHumanReadable).
		WaitLogBackupCheckpoint(brHost, srcTidb, logBackupOpts, &tsHumanReadable, 10*time.Minute).
		UpdateStructFieldByPointer(logRestoreOpts, "RestoredTs", &tsNow).
		UpdateStructFieldByPointer(logRestoreOpts, "FullBackupStorage", &emptyStr).
		BrRestorePoint(brHost, dstTidb, logRestoreOpts).
		CheckUserDatabasesConsistency(srcTidb, dstTidb).
		McRemove(mcRmOpts, mcHost).
		Build()
}

func main() {
	_, logger := utils.GetCtxAndLogger()
	ctx := steps.New(context.Background())
	readSql("./sql0.sql", sql0)
	readSql("./sql1.sql", sql1)
	// stop log backup task, in case previous task not stopped
	if err := CleanUp().Execute(ctx); err != nil {
		logger.Fatal("Cleanup failed")
	}
	if err := BrPiTR().Execute(ctx); err != nil {
		logger.Fatal("Br PiTR DDL Comp failed", zap.Error(err))
	}
	logger.Info("Br PiTR DDL Comp passed")
}
