package aws

import (
	"fmt"
	"testing"
	"time"

	l4g "github.com/alecthomas/log4go"
	"github.com/avast/retry-go"
	"github.com/dailymotion/allure-go"
	"github.com/tidbcloud/qa-PlatformAPIAuto/common"
	"github.com/tidbcloud/qa-PlatformAPIAuto/util"
	dpCommon "github.com/tidbcloud/qa-PlatformAPIAuto/webScene/common"
	"github.com/tidbcloud/qa-PlatformAPIAuto/webScene/rg"
	"github.com/tidbcloud/qa-gourd/ProductModel"
	"github.com/tidbcloud/qa-gourd/ProductService"
)

func ExecuteRgSmokeUseCase(t *testing.T) {
	uniqueKey := fmt.Sprintf("%s_%s",
		dpCommon.SCHEMA_RG_SMOKE_USE_CASE, util.FnvHash(common.Envn+string(dpCommon.CloudProviderTypeAWS)+common.GetDbassRequestInfo(t).CommonEnv.Email))
	test := &RGSmokeUseCaseSceneTest{
		rg.CommonSceneTest{
			SceneTest: dpCommon.SceneTest{
				T:                t,
				DbaasRequestInfo: common.GetDbassRequestInfo(t),
				TestSchemaName:   uniqueKey,
			},
		},
	}
	test.Run()
}

type RGSmokeUseCaseSceneTest struct {
	rg.CommonSceneTest
}

func (s *RGSmokeUseCaseSceneTest) Run() {
	allure.Test(s.T,
		allure.Epic(fmt.Sprintf("%s-%s", dpCommon.CloudProviderTypeAWS, dpCommon.RGSmokeUseCase)),
		allure.Feature(string(dpCommon.RGSmokeUseCase)),
		allure.Description(string(dpCommon.RGSmokeUseCase)),
		allure.Action(func() {
			s.CreateTiDBClusterStep()
			s.ExecuteTidbDataStep(s.ClusterId, s.InitTidb)
			s.ExecuteTidbDataStep(s.ClusterId, s.CreateFullStageTable)
			s.ExecuteTidbDataStep(s.ClusterId, s.InsertToFullStageTable)
			s.ExecuteTidbDataStep(s.SecondaryClusterId, s.InitTidb)

			s.CreateRecoveryGroup()
			s.WaitCreateRecoveryGroupReady()
			s.ExecuteTiDBDataWithRetryStep(s.SecondaryClusterId, s.CheckFullStageTableCount)

			s.ExecuteTidbDataStep(s.ClusterId, s.CreateIncreStageTable)
			s.ExecuteTidbDataStep(s.ClusterId, s.InsertToIncreStageTable)
			s.ExecuteTiDBDataWithRetryStep(s.SecondaryClusterId, s.CheckTableIncreTestData)

			s.FailoverRecoveryGroup()
			s.WaitFailoverRecoveryGroupReady()

			s.ReprotectRecoveryGroup()
			s.WaitReprotectRecoveryGroupReady()
			s.ExecuteTiDBDataWithRetryStep(s.ClusterId, s.CheckFullStageTableCount)
			s.ExecuteTiDBDataWithRetryStep(s.ClusterId, s.CheckTableIncreTestData)

			s.DeleteRecoveryGroup()
			time.Sleep(2 * 60 * time.Second)
		}))
}

func (s *RGSmokeUseCaseSceneTest) CreateTiDBClusterStep() {
	allure.Step(
		allure.Description("create tidb cluster"),
		allure.Action(func() {
			errCh := make(chan error, 2)
			go func() {
				if GetPrimaryClusterId() != "" {
					s.ClusterId = primaryClusterId
					errCh <- nil
					return
				}
				masterWebRequestInfo := ProductService.GetWebAPIRequestInfo(&testing.T{}, ProductModel.Specify)
				primaryClusterId, err := masterWebRequestInfo.CreateMinDedicatedCluster()
				if err != nil {
					errCh <- err
					return
				}
				s.ClusterId = primaryClusterId
				SetPrimaryClusterId(primaryClusterId)
				errCh <- nil
			}()

			go func() {
				if GetSecondaryClusterId() != "" {
					s.SecondaryClusterId = secondaryClusterId
					errCh <- nil
					return
				}
				secondaryWebRequestInfo := ProductService.GetWebAPIRequestInfo(&testing.T{}, ProductModel.Specify)
				secondaryClusterId, err := secondaryWebRequestInfo.CreateMinDedicatedCluster()
				if err != nil {
					errCh <- err
					return
				}
				s.SecondaryClusterId = secondaryClusterId
				SetSecondaryClusterId(secondaryClusterId)
				errCh <- nil
			}()

			// 设置一个超时时间
			timeout := time.After(2 * time.Hour)
			// 等待协程执行完毕，并从通道中取出错误
			for i := 0; i < 2; i++ {
				select {
				case err := <-errCh:
					if err != nil {
						// 发生错误，进行处理
						l4g.Error(err)
						allure.FailNow(err)
						return
					}
				case <-timeout:
					// 超时，返回错误信息
					err := fmt.Errorf("create cluster timeout")
					l4g.Error(err)
					allure.FailNow(err)
					return
				}
			}
		}),
	)
}

func (s *RGSmokeUseCaseSceneTest) CreateRecoveryGroup() {
	allure.Step(
		allure.Description("create recovery group"),
		allure.Action(func() {
			err := retry.Do(
				func() error {
					createResp, err := s.createRecoveryGroup()
					if err != nil {
						return err
					}
					if createResp.Id == "" {
						return fmt.Errorf("rg id is empty")
					}
					s.RecoveryGroupId = createResp.Id
					l4g.Info("recovery group id is: %s", s.RecoveryGroupId)
					return nil
				},
				retry.DelayType(retry.FixedDelay),
				retry.Delay(time.Second*15),
				retry.Attempts(6),
				retry.OnRetry(func(n uint, err error) {
					l4g.Info("#%d: %s\n", n, err)
				}),
			)
			if err != nil {
				l4g.Error(err)
				allure.FailNow(err)
			}
		}),
	)
}

func (s *RGSmokeUseCaseSceneTest) createRecoveryGroup() (*dpCommon.CreateRecoveryGroupResp, error) {
	pathParam := map[string]string{}
	request := dpCommon.CreateRecoveryGroupRequest{
		Setting: &dpCommon.RecoveryGroupSetting{
			DisplayName:      s.TestSchemaName,
			TableFilters:     []string{fmt.Sprintf("%s.*", s.TestSchemaName)},
			ResiliencyPolicy: dpCommon.ResiliencyPolicyType_WITHOUT_CONSISTENCY_GUARANTEE,
			Primary:          s.ClusterId,
			Secondaries:      []string{s.SecondaryClusterId},
		},
		Labels: map[string]string{
			"org_id":     s.DbaasRequestInfo.CommonEnv.OrgId,
			"project_id": s.DbaasRequestInfo.CommonEnv.ProjectId,
		},
	}
	callResp := s.DbaasRequestInfo.DbassRequest(s.T, "createRecoveryGroup", request, pathParam, nil, nil)
	httpInfo := common.ParseHttpInfo(callResp)
	if httpInfo.RespStatusCode != common.StatusOK {
		return nil, fmt.Errorf("status code is: %d, http details is: %s", httpInfo.RespStatusCode, httpInfo.ToString())
	}
	l4g.Info("create recovery group http info is %s", httpInfo.ToString())

	createResp := &dpCommon.CreateRecoveryGroupResp{}
	httpInfo.UnmarshalRespBody(createResp)
	return createResp, nil
}

func (s *RGSmokeUseCaseSceneTest) getRecoveryGroup() (*dpCommon.GetRecoveryGroupResp, error) {
	pathParam := map[string]string{
		"recovery_group_id": s.RecoveryGroupId,
	}
	queryParam := map[string]string{
		"org_id":     s.DbaasRequestInfo.CommonEnv.OrgId,
		"project_id": s.DbaasRequestInfo.CommonEnv.ProjectId,
	}
	callResp := s.DbaasRequestInfo.DbassRequest(s.T, "getRecoveryGroup", nil, pathParam, queryParam, nil)
	httpInfo := common.ParseHttpInfo(callResp)
	if httpInfo.RespStatusCode != common.StatusOK {
		return nil, fmt.Errorf("status code is: %d, http details is: %s", httpInfo.RespStatusCode, httpInfo.ToString())
	}
	l4g.Info("get recovery group http info is %s", httpInfo.ToString())

	getResp := &dpCommon.GetRecoveryGroupResp{}
	httpInfo.UnmarshalRespBody(getResp)
	return getResp, nil
}

func (s *RGSmokeUseCaseSceneTest) failoverRecoveryGroup() error {
	pathParam := map[string]string{
		"recovery_group_id": s.RecoveryGroupId,
	}
	request := dpCommon.FailoverRecoveryGroupRequest{
		NewPrimaryId: s.SecondaryClusterId,
		Labels: map[string]string{
			"org_id":     s.DbaasRequestInfo.CommonEnv.OrgId,
			"project_id": s.DbaasRequestInfo.CommonEnv.ProjectId,
		},
	}
	callResp := s.DbaasRequestInfo.DbassRequest(s.T, "failoverRecoveryGroup", request, pathParam, nil, nil)
	httpInfo := common.ParseHttpInfo(callResp)
	if httpInfo.RespStatusCode != common.StatusOK {
		return fmt.Errorf("status code is: %d, http details is: %s", httpInfo.RespStatusCode, httpInfo.ToString())
	}
	l4g.Info("failover recovery group http info is %s", httpInfo.ToString())
	return nil
}

func (s *RGSmokeUseCaseSceneTest) reprotectRecoveryGroup() error {
	pathParam := map[string]string{
		"recovery_group_id": s.RecoveryGroupId,
	}
	request := dpCommon.ReprotectRecoveryGroupRequest{
		ReprotectedIds: []string{s.ClusterId},
		Labels: map[string]string{
			"org_id":     s.DbaasRequestInfo.CommonEnv.OrgId,
			"project_id": s.DbaasRequestInfo.CommonEnv.ProjectId,
		},
	}
	callResp := s.DbaasRequestInfo.DbassRequest(s.T, "reprotectRecoveryGroup", request, pathParam, nil, nil)
	httpInfo := common.ParseHttpInfo(callResp)
	if httpInfo.RespStatusCode != common.StatusOK {
		return fmt.Errorf("status code is: %d, http details is: %s", httpInfo.RespStatusCode, httpInfo.ToString())
	}
	l4g.Info("reprotect recovery group http info is %s", httpInfo.ToString())
	return nil
}

func (s *RGSmokeUseCaseSceneTest) deleteRecoveryGroup() error {
	pathParam := map[string]string{
		"recovery_group_id": s.RecoveryGroupId,
	}
	queryParam := map[string]string{
		"org_id":     s.DbaasRequestInfo.CommonEnv.OrgId,
		"project_id": s.DbaasRequestInfo.CommonEnv.ProjectId,
	}
	callResp := s.DbaasRequestInfo.DbassRequest(s.T, "deleteRecoveryGroup", nil, pathParam, queryParam, nil)
	httpInfo := common.ParseHttpInfo(callResp)
	if httpInfo.RespStatusCode != common.StatusOK {
		return fmt.Errorf("status code is: %d, http details is: %s", httpInfo.RespStatusCode, httpInfo.ToString())
	}
	l4g.Info("delete recovery group http info is %s", httpInfo.ToString())
	return nil
}

func (s *RGSmokeUseCaseSceneTest) WaitCreateRecoveryGroupReady() {
	s.waitCreateRecoveryGroupOnState(dpCommon.RecoveryGroupState_AVAILABLE)
}

func (s *RGSmokeUseCaseSceneTest) WaitFailoverRecoveryGroupReady() {
	s.waitCreateRecoveryGroupOnState(dpCommon.RecoveryGroupState_UNPROTECTED)
}

func (s *RGSmokeUseCaseSceneTest) WaitReprotectRecoveryGroupReady() {
	s.waitCreateRecoveryGroupOnState(dpCommon.RecoveryGroupState_AVAILABLE)
}

func (s *RGSmokeUseCaseSceneTest) waitCreateRecoveryGroupOnState(state dpCommon.RecoveryGroupState) {
	allure.Step(
		allure.Description(fmt.Sprintf("wait for recovery group on state: %s", state)),
		allure.Action(func() {
			err := retry.Do(
				func() error {
					recoveryGroup, err := s.getRecoveryGroup()
					if err != nil {
						return err
					}

					if recoveryGroup.State != state {
						return fmt.Errorf("recovery group is: %s", recoveryGroup.State)
					}
					return nil
				},
				retry.DelayType(retry.FixedDelay),
				retry.Delay(time.Second*15),
				retry.Attempts(4*60),
				retry.OnRetry(func(n uint, err error) {
					l4g.Info("#%d: %s\n", n, err)
				}),
			)

			if err != nil {
				l4g.Error(err)
				allure.FailNow(err)
			}
		}),
	)
}

func (s *RGSmokeUseCaseSceneTest) FailoverRecoveryGroup() {
	allure.Step(
		allure.Description("failover recovery group"),
		allure.Action(func() {
			err := retry.Do(
				func() error {
					err := s.failoverRecoveryGroup()
					if err != nil {
						return err
					}
					return nil
				},
				retry.DelayType(retry.FixedDelay),
				retry.Delay(time.Second*15),
				retry.Attempts(6),
				retry.OnRetry(func(n uint, err error) {
					l4g.Info("#%d: %s\n", n, err)
				}),
			)
			if err != nil {
				l4g.Error(err)
				allure.FailNow(err)
			}
		}),
	)
}

func (s *RGSmokeUseCaseSceneTest) ReprotectRecoveryGroup() {
	allure.Step(
		allure.Description("reprotect recovery group"),
		allure.Action(func() {
			err := retry.Do(
				func() error {
					err := s.reprotectRecoveryGroup()
					if err != nil {
						return err
					}
					return nil
				},
				retry.DelayType(retry.FixedDelay),
				retry.Delay(time.Second*15),
				retry.Attempts(6),
				retry.OnRetry(func(n uint, err error) {
					l4g.Info("#%d: %s\n", n, err)
				}),
			)
			if err != nil {
				l4g.Error(err)
				allure.FailNow(err)
			}
		}),
	)
}

func (s *RGSmokeUseCaseSceneTest) DeleteRecoveryGroup() {
	allure.Step(
		allure.Description("delete recovery group"),
		allure.Action(func() {
			err := retry.Do(
				func() error {
					err := s.deleteRecoveryGroup()
					if err != nil {
						return err
					}
					return nil
				},
				retry.DelayType(retry.FixedDelay),
				retry.Delay(time.Second*15),
				retry.Attempts(6),
				retry.OnRetry(func(n uint, err error) {
					l4g.Info("#%d: %s\n", n, err)
				}),
			)
			if err != nil {
				l4g.Error(err)
				allure.FailNow(err)
			}
		}),
	)
}
