package cases

import (
	"fmt"
	"time"

	l4g "github.com/alecthomas/log4go"
	"github.com/avast/retry-go"
	"github.com/dailymotion/allure-go"
	opsmod "github.com/tidbcloud/qa-PlatformAPIAuto/models/ops"
	"github.com/tidbcloud/qa-PlatformAPIAuto/webScene/ops"
	"github.com/tidbcloud/qa-gourd/ProductService"
)

type UpgradeClusterTest struct {
	*ops.OpsTestBase

	toVersion string

	OriginConfig  *opsmod.CloudconfigConfiguration
	PreviewResult *opsmod.CloudconfigPreviewConfigurationChangeResp
	Change        *opsmod.GithubComTidbcloudDevopsEntityCloudconfigConfigurationChange
}

func RunUpgradeClusterTest(w *ProductService.WebRequestInfo, urls map[string]ProductService.UrlInfo) {
	base := &ops.OpsTestBase{}
	base.Init(w, urls)
	t := &UpgradeClusterTest{
		OpsTestBase: base,
		toVersion:   "v7.6.0",
	}
	t.Run()
}

func (t *UpgradeClusterTest) Run() {
	allure.Test(
		t.T,
		allure.Epic("TestConfigCenter"),
		allure.Feature("UpgradeCluster"),
		allure.Description("upgrade a cluster config"),
		allure.Action(func() {
			// create and delete the cluster in test entrypoint

			t.GetClusterConfigStep()

			t.PreviewUpgrade()

			t.CreateConfigChange()

			t.ApplyConfigChange()
		}),
	)
}

func (t *UpgradeClusterTest) GetClusterConfigStep() {
	allure.Step(
		allure.Description("get current cluster config"),
		allure.Action(func() {
			resp, err := t.API.GetClusterConfiguration(t.W.Cluster.ClusterId)
			if err != nil {
				allure.FailNow(err)
				return
			}
			t.OriginConfig = resp.Configuration
		}),
	)
}

func (t *UpgradeClusterTest) PreviewUpgrade() {
	allure.Step(
		allure.Description("preview config change"),
		allure.Action(func() {
			req := &opsmod.CloudconfigPreviewClusterConfigurationChangeReq{
				ClusterID:   &t.OriginConfig.TargetCluster.ClusterID,
				ClusterType: &t.OriginConfig.TargetCluster.ClusterType,
				Version: &opsmod.CloudconfigChangeVersionReq{
					ToVersion: t.toVersion,
				},
			}

			resp, err := t.API.PreviewClusterConfiguration(req)
			if err != nil {
				allure.FailNow(err)
				return
			}
			t.PreviewResult = resp

			// check result
			err = t.checkChange(resp.Change)
			if err != nil {
				allure.FailNow(err)
				return
			}
		}),
	)
}

func (t *UpgradeClusterTest) CreateConfigChange() {
	allure.Step(
		allure.Description("create config change and check"),
		allure.Action(func() {
			req := &opsmod.CloudconfigCreateClusterConfigurationChangeReq{
				ClusterID:   &t.OriginConfig.TargetCluster.ClusterID,
				ClusterType: &t.OriginConfig.TargetCluster.ClusterType,
				ApplyPolicy: opsmod.CloudconfigParameterApplyPolicyRollingRestart,
				Reason:      t.MagicReason(),
				Version: &opsmod.CloudconfigChangeVersionReq{
					ToVersion: t.toVersion,
				}}

			resp, err := t.API.CreateClusterConfigChange(req)
			if err != nil {
				allure.FailNow(err)
				return
			}
			t.Change = &opsmod.GithubComTidbcloudDevopsEntityCloudconfigConfigurationChange{ // init a change with id to sync it.
				ID: resp.ChangeID,
			}

			err = t.syncCurrentChange()
			if err != nil {
				allure.FailNow(err)
				return
			}

			// check result
			err = t.checkChange(t.Change)
			if err != nil {
				allure.FailNow(err)
				return
			}
		}),
	)
}

func (t *UpgradeClusterTest) ApplyConfigChange() {
	allure.Step(
		allure.Description("apply config change"),
		allure.Action(func() {
			err := t.waitChangeToAppliable()
			if err != nil {
				allure.FailNow(err)
				return
			}

			err = t.API.ApplyConfigChange(t.Change.ID)
			if err != nil {
				allure.FailNow(err)
				return
			}

			// TODO: check result

		}),
	)
}

func (t *UpgradeClusterTest) waitChangeToAppliable() error {
	err := retry.Do(
		func() error {
			err := t.syncCurrentChange()
			if err != nil {
				return err
			}
			if t.Change.Phase != opsmod.CloudconfigConfigurationChangePhaseWaitForApply {
				return fmt.Errorf("change(%d) phase is '%s' but not 'wait_for_apply'", t.Change.ID, t.Change.Phase)
			}
			return nil
		},
		retry.DelayType(retry.FixedDelay),
		retry.Delay(time.Second*10),
		retry.Attempts(18),
		retry.OnRetry(func(n uint, err error) {
			l4g.Info("#%d: %s\n", n, err)
		}),
	)

	return err
}

func (t *UpgradeClusterTest) syncCurrentChange() error {
	change, err := t.API.GetConfigChange(t.Change.ID)
	if err != nil {
		return err
	}
	t.Change = change
	return nil
}

func (t *UpgradeClusterTest) checkChange(change *opsmod.GithubComTidbcloudDevopsEntityCloudconfigConfigurationChange) error {
	if change.Scope.Version == nil {
		return fmt.Errorf("version change is nil")
	}
	if change.Scope.Version.To != t.toVersion {
		return fmt.Errorf("version change is not expected, got:%s exp:%s", change.Scope.Version.To, t.toVersion)
	}
	return nil
}
