package common

import (
	"database/sql"
	"fmt"
	"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/models"
)

func (s *SceneTest) ExecuteTidbDataWithDefaultClusterIDStep(dataFunc MySQLStatement) {
	s.ExecuteTidbDataStep(s.ClusterId, dataFunc)
}

func (s *SceneTest) ExecuteTidbDataStep(clusterId string, dataFunc MySQLStatement) {
	allure.Step(
		allure.Description("execute tidb data step"),
		allure.Action(func() {
			db, err := s.GetTiDBConnection(clusterId)
			defer db.Close()
			if err != nil {
				errorMsg := fmt.Errorf("connect to tidb:%s occur error: %s", clusterId, err)
				l4g.Error(errorMsg)
				allure.FailNow(errorMsg)
			}

			err = dataFunc(db)
			if err != nil {
				errorMsg := fmt.Errorf("init tidb:%s data occur error: %s", clusterId, err)
				l4g.Error(errorMsg)
				allure.FailNow(errorMsg)
			}
		}),
	)
}

func (s *SceneTest) ExecuteTiDBDataWithRetryStep(clusterId string, checkFunc MySQLStatement) {
	allure.Step(
		allure.Description("check TiDB data step"),
		allure.Action(func() {
			db, err := s.GetTiDBConnection(clusterId)
			defer db.Close()
			if err != nil {
				errorMsg := fmt.Errorf("connect TiDB:%s occur error: %s", clusterId, err)
				l4g.Error(errorMsg)
				allure.FailNow(errorMsg)
			}
			err = retry.Do(
				func() error {
					return checkFunc(db)
				},
				retry.DelayType(retry.FixedDelay),
				retry.Delay(time.Second*10),
				retry.Attempts(30),
				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 *SceneTest) GetTiDBConnection(clusterID string) (*sql.DB, error) {
	pathParam := map[string]string{
		"org_id":     s.DbaasRequestInfo.CommonEnv.OrgId,
		"project_id": s.DbaasRequestInfo.CommonEnv.ProjectId,
		"cluster_id": clusterID,
	}
	var cluster *models.CentralCluster

	err := common.Retry(10, time.Second*20, func() error {
		callResp := s.DbaasRequestInfo.DbassRequest(s.T, "getClusterInfo", nil, pathParam, nil, nil)
		httpInfo := common.ParseHttpInfo(callResp)
		if httpInfo.RespStatusCode != common.StatusOK {
			errorMsg := fmt.Errorf("status code is: %d, http details is: %s", httpInfo.RespStatusCode, httpInfo.ToString())
			l4g.Error(errorMsg)
			return errorMsg
		}

		resp := &models.CentralGetClusterResp{}
		httpInfo.UnmarshalRespBody(resp)
		if !resp.Cluster.Endpoint.Ready {
			errMsg := fmt.Errorf("endpoint of cluster %s is not ready", clusterID)
			l4g.Error(errMsg)
			return errMsg
		}

		cluster = resp.Cluster
		return nil
	})
	if err != nil {
		allure.FailNow(err)
	}

	tidbAddress := cluster.Endpoint.Address
	tidbPort := cluster.Endpoint.Port

	return common.ConnectTidbUseTLS(*tidbAddress, (int64)(*tidbPort), s.DbaasRequestInfo.CommonEnv.DbPassword, "test", "root")
}

func (s *SceneTest) InitTidb(db *sql.DB) error {
	err := DropSchema(db, s.TestSchemaName)
	if err != nil {
		return err
	}
	err = CreateSchema(db, s.TestSchemaName)
	if err != nil {
		return err
	}
	return s.CreateFullStageTable(db)
}

func (s *SceneTest) SetGC720h(db *sql.DB) error {
	err := SetGCTime(db, "720h")
	return err
}

func (s *SceneTest) SetGC10m(db *sql.DB) error {
	err := SetGCTime(db, "10m")
	return err
}

func (s *SceneTest) CreateFullStageTable(db *sql.DB) error {
	err := CreateTableWithUniqueKey(db, s.TestSchemaName, string(TABLE_TEST1))
	if err != nil {
		return err
	}
	return CreateTableWithoutUniqueKey(db, s.TestSchemaName, string(TABLE_TEST2))
}

func (s *SceneTest) InsertToFullStageTable(db *sql.DB) error {
	err := InsertDataToTable(db, DML_TEMPLATE_INSERT_ONE_ROW, s.TestSchemaName, string(TABLE_TEST1))
	if err != nil {
		return err
	}
	err = InsertDataToTable(db, DML_TEMPLATE_INSERT_TWO_ROW, s.TestSchemaName, string(TABLE_TEST2))
	if err != nil {
		return err
	}
	return nil
}

func (s *SceneTest) CreateIncreStageTable(db *sql.DB) error {
	return CreateTableWithUniqueKey(db, s.TestSchemaName, string(TABLE_TEST3))
}

func (s *SceneTest) InsertToIncreStageTable(db *sql.DB) error {
	return InsertDataToTable(db, DML_TEMPLATE_INSERT_THREE_ROW, s.TestSchemaName, string(TABLE_TEST3))
}

func (s *SceneTest) AddClusterTraffic(clusterId string) {
	allure.Step(
		allure.Description("add cluster traffic"),
		allure.Action(func() {
			err := retry.Do(
				func() error {
					return s.addTraffic(clusterId)
				},
				retry.DelayType(retry.FixedDelay),
				retry.Delay(time.Second*10),
				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 *SceneTest) addTraffic(clusterId string) error {
	pathParam := map[string]string{
		"org_id":     s.DbaasRequestInfo.CommonEnv.OrgId,
		"project_id": s.DbaasRequestInfo.CommonEnv.ProjectId,
		"cluster_id": clusterId,
	}
	filter := common.NewTrafficFilter(s.DbaasRequestInfo.CommonEnv.Cicr, "", false)
	callResp := s.DbaasRequestInfo.DbassRequest(s.T, "addFilter", filter, pathParam, nil, nil)

	httpInfo := common.ParseHttpInfo(callResp, common.ReqBodyOp(filter))
	if httpInfo.RespStatusCode != common.StatusOK {
		return fmt.Errorf("status code is: %d, http details is: %s", httpInfo.RespStatusCode, httpInfo.ToString())
	}
	return nil
}

func (s *SceneTest) DeleteCluster(clusterId string) {
	allure.Step(
		allure.Description("add cluster traffic"),
		allure.Action(func() {
			err := retry.Do(
				func() error {
					return s.deleteCluster(clusterId)
				},
				retry.DelayType(retry.FixedDelay),
				retry.Delay(time.Second*10),
				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 *SceneTest) deleteCluster(clusterId string) error {
	pathParam := map[string]string{
		"org_id":     s.DbaasRequestInfo.CommonEnv.OrgId,
		"project_id": s.DbaasRequestInfo.CommonEnv.ProjectId,
		"cluster_id": clusterId,
	}
	callResp := s.DbaasRequestInfo.DbassRequest(s.T, "delCluster", nil, 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())
	}
	return nil
}
