package upgradepkg

import (
	"context"
	"database/sql"
	"fmt"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/ngaut/log"
	. "github.com/onsi/ginkgo"
	. "github.com/pingcap/endless/pkg/matcher"
	"github.com/pingcap/test-infra/sdk/resource"
	"go.uber.org/zap"
)

func UpgradeClusterByImage(
	ctx context.Context,
	tc resource.TiDBCluster,
	target string,
	downstream resource.TiDBCluster,
	timeout time.Duration,
	nodelist []string,
) error {
	for _, comtype := range nodelist {
		if target == "" {
			log.Info("No image provided, no need to upgrade.")
			continue
		}

		targetnode := getResourceTypeByString(comtype)
		log.Info("targetVersion:", target)
		targetVersion := strings.TrimPrefix(target, "v")
		if targetVersion == "master" {
			masterVersion := GetCompVersion(ctx, downstream, comtype)
			targetVersion = masterVersion
			log.Info("master version info", zap.String("master version", targetVersion))
		}

		if strings.Contains(targetVersion, "-") {
			targetVersion = strings.Split(targetVersion, "-")[0]
		}

		curVersion := GetCompVersion(ctx, tc, comtype)
		if strings.Contains(curVersion, targetVersion) {
			log.Info("Seems no need to upgrade.", zap.String("targetVersion", target), zap.String("curVersion", curVersion))
			continue
		}
		log.Info("Now begin to upgrade ", zap.String("comptype", comtype), zap.String("targetVersion", target))
		err := tc.Upgrade(
			resource.TiDBClusterUpgradeOptions{
				ComponentUpgradeOption: &resource.TiDBClusterComponentUpgradeOption{
					Target: targetnode,
					// Image:  image,
					Version: target,
				},
				Timeout: timeout})
		if err != nil {
			log.Info("upgrade fail")
			Fail(err.Error())
		}

		log.Info("Upgrade succeeded.", zap.String("comptype", comtype))
		WaitFor(ctx, "Waiting for information update to w/a bug#https://github.com/pingcap/tidb/issues/30907.", 3*time.Minute)
		//Restart workload after upgrade tidb succeeded.

		if targetVersion == "master" {
			version := GetCompVersion(ctx, tc, comtype)
			log.Info("Just get version for master:", zap.String("Version", version))
			CheckClusterVersion(ctx, tc, targetVersion, comtype)
		} else {
			CheckClusterVersion(ctx, tc, target, comtype)
		}

	}
	return nil
}
func getResourceTypeByString(comType string) resource.TiDBCompType {
	var target resource.TiDBCompType
	if comType == "tikv" {
		target = resource.TiKV
	} else if comType == "tidb" {
		target = resource.TiDB
	} else if comType == "pd" {
		target = resource.PD
	} else if comType == "tiflash" {
		target = resource.TiFlash
	} else if comType == "ticdc" {
		target = resource.TiCDC
	}

	return target
}
func GetCompVersion(ctx context.Context, tc resource.TiDBCluster, comType string) string {
	db := Try(tc.GetDB("")).(*sql.DB)
	defer db.Close()
	var count int
	var version string
	Try(db.QueryRow(fmt.Sprintf(`select count(distinct(version)) from information_schema.cluster_info where type="%s"`, comType)).Scan(&count))
	if count != 1 {
		log.Warn("Instance version is not consistent.")
		rows, _ := db.QueryContext(ctx, fmt.Sprintf(`select instance,version from information_schema.cluster_info where type="%s"`, comType))
		defer rows.Close()
		for rows.Next() {
			var version string
			var instance string
			Try(rows.Scan(&instance, &version))
			log.Info("Inconsistent version detail", zap.String(instance, version))
		}
		return "Invalid"
	}
	Try(db.QueryRow(fmt.Sprintf(`select distinct(version) from information_schema.cluster_info where type="%s"`, comType)).Scan(&version))
	//log.Info("Current version", zap.String(comType, version))
	return version
}
func CheckClusterVersion(ctx context.Context, tc resource.TiDBCluster, targetVersion string, comType string) {
	shortVersion := targetVersion
	shortVersion = strings.TrimPrefix(targetVersion, "v")
	if strings.Contains(targetVersion, "-") {
		shortVersion = strings.Split(shortVersion, "-")[0]
	}
	curVersion := GetCompVersion(ctx, tc, comType)
	log.Info("Check version", zap.String("current version", curVersion), zap.String("target version", shortVersion))

	//Maybe hit issue https://github.com/pingcap/tidb/issues/30907
	//Expect(strings.Contains(curVersion, shortVersion)).Should(BeTrue())
	if !strings.Contains(curVersion, shortVersion) {
		log.Warn("Maybe hit issue https://github.com/pingcap/tidb/issues/30907")
	}

}
func WaitFor(ctx context.Context, reason string, duration time.Duration) {
	log.Info(reason, zap.Duration("wait", duration), zap.String("callBy", PrintCallerName()))
	SleepWithCtx(ctx, duration)
}
func SleepWithCtx(ctx context.Context, timeout time.Duration) {
	select {
	case <-time.After(timeout):
		return
	case <-ctx.Done():
		return
	}
}
func PrintCallerName() string {
	_, file, line, _ := runtime.Caller(2)
	_, fileName := filepath.Split(file)
	return fmt.Sprintf("%s:%d", fileName, line)
}
