package common

import (
	"fmt"
	"math/rand"
	"strconv"
	"sync"
	"time"

	"github.com/hashicorp/go-version"
	"github.com/shopspring/decimal"
	"github.com/tidbcloud/qa-gourd/ProductModel"
	"github.com/tidbcloud/qa-gourd/ProductService"

	l4g "github.com/alecthomas/log4go"
	"github.com/gavv/httpexpect/v2"
)

var randomNumberMutex sync.Mutex
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
var lowerletters = []rune("abcdefghijklmnopqrstuvwxyz1234567890")

func RandStr(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letters[rand.Intn(len(letters))]
	}
	return string(b)
}
func RandLowerStr(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = lowerletters[rand.Intn(len(lowerletters))]
	}
	return string(b)
}
func GenTempName() string {
	//并发状态下cluster name 可能会重名，需要特殊处理
	randomNumberMutex.Lock()
	defer randomNumberMutex.Unlock()

	time.Sleep(5 * time.Second)
	name := strconv.FormatInt(time.Now().Unix(), 10)
	return name
}
func CompareSQLResult(actual_result, expect_result *httpexpect.Array) bool {
	if len(actual_result.Iter()) == len(expect_result.Iter()) {
		for idx, value := range actual_result.Iter() {
			if len(value.Array().Iter()) != len(expect_result.Iter()[idx].Array().Iter()) {
				return false
			}
			for idx2, value2 := range value.Array().Iter() {
				value2.Equal(expect_result.Iter()[idx].Array().Iter()[idx2].Raw())
			}
		}
	} else {
		return false
	}

	return true
}

// get region of app's target clusters
func GetRegionofAPPCluster(APPClustersList *APPClusters, Cluster_Id string) (string, error) {
	if APPClustersList == nil {
		return "", fmt.Errorf("APPClustersList is nil")
	}

	for _, cluster := range APPClustersList.Data {
		if cluster.ClusterID == Cluster_Id {
			return cluster.Region, nil
		}
	}
	return "", fmt.Errorf("Can not find cluster %s in APPClustersList", Cluster_Id)
}

// if return true : firstVersion > lastVersion
func CompareVersion(firstVersion, lastVersion string) (bool, error) {
	if firstVersion == "" || lastVersion == "" {
		return false, fmt.Errorf("Empty Input")
	}
	v1, err := version.NewVersion(firstVersion)
	if err != nil {
		return false, err
	}
	v2, err := version.NewVersion(lastVersion)
	if err != nil {
		return false, err
	}

	if v1.GreaterThanOrEqual(v2) {
		return true, nil
	}

	if v1.LessThan(v2) {
		return false, nil
	}

	return false, fmt.Errorf("CompareVersion failed")
}

func GenQueryOKExpect(rowNumber int) string {
	if rowNumber < 0 {
		l4g.Info("rowNumber is less than 0")
		return ""
	}
	if rowNumber == 1 {
		return fmt.Sprintf("Query OK, %d %s affected", rowNumber, "row")
	}
	return fmt.Sprintf("Query OK, %d %s affected", rowNumber, "rows")
}

// return target Org from UserProfile by org_id
func GetOrgsFromUserProfileV2(UserProfile *ProductModel.UserProfileV2) ([]*ProductModel.UserProfileOrg, error) {
	if UserProfile == nil {
		return nil, fmt.Errorf("UserProfile is nil")
	}

	if UserProfile.Orgs == nil {
		return nil, fmt.Errorf("orgs is nil")
	}

	return UserProfile.Orgs, nil
}

// return target Org from UserProfile by org_id
func GetOrgInfoFromUserProfileV2(UserProfile *ProductModel.UserProfileV2, orgId string) (*ProductModel.UserProfileOrg, error) {
	if UserProfile == nil {
		return nil, fmt.Errorf("UserProfile is nil")
	}

	for _, org := range UserProfile.Orgs {
		if org.Id == orgId {
			return org, nil
		}
	}

	return nil, fmt.Errorf("Can not find org [%s] in UserProfile", orgId)
}

// return target Projects from UserProfile by org_id
func GetProjectsFromUserProfileV2(UserProfile *ProductModel.UserProfileV2, orgId string) (*[]ProductModel.UserProfileProject, error) {
	if UserProfile == nil {
		return nil, fmt.Errorf("UserProfile is nil")
	}

	var Projects []ProductModel.UserProfileProject
	for _, project := range UserProfile.Projects {
		if project.OrgId == orgId {
			Projects = append(Projects, *project)
		}
	}

	if len(Projects) == 0 {
		return nil, fmt.Errorf("Can not find projects in org [%s] in UserProfile", orgId)
	}

	return &Projects, nil
}

// return target Org from OrgsV1 by org_id
func GetOrgInfoFromOrgsV1(orgList *OrgList, orgId string) (*Organization, error) {
	if orgList == nil {
		return nil, fmt.Errorf("orgList is nil")
	}

	for _, org := range orgList.Orgs {
		if org.Id == orgId {
			return org, nil
		}
	}

	return nil, fmt.Errorf("Can not find org [%s] in UserProfile", orgId)
}

func GetOrgInfoDetailFromOrgsV1(orgList *OrgList, orgId string, target_param string) string {
	OrgInfo, err := GetOrgInfoFromOrgsV1(orgList, orgId)
	if err != nil {
		l4g.Info("GetOrgInfoFromOrgsV1 error : %v", err)
	}
	switch target_param {
	case "OrgType":
		return OrgInfo.OrgType
	}

	return ""
}

func SumCredits(creditslist *Credits) decimal.Decimal {
	sum := decimal.Zero
	for _, item := range *creditslist {
		sum = sum.Add(item.TotalCredits)
	}
	return sum
}

func SumRemainingCredits(creditslist *Credits) decimal.Decimal {
	sum := decimal.Zero
	for _, item := range *creditslist {
		sum = sum.Add(item.RemainingCredits)
	}
	return sum
}

func SumValidRemainingCredits(creditslist *Credits) decimal.Decimal {
	sum := decimal.Zero
	for _, item := range *creditslist {
		if item.Status == "active" {
			sum = sum.Add(item.RemainingCredits)
		}
	}
	return sum
}

func CheckHasSchema(data *httpexpect.Array, expectSchema string) bool {
	for _, item := range data.Iter() {
		if item.Object().Path("$.name").String().Raw() == expectSchema {
			return true
		}
	}
	return false
}
func CheckHasColumn(data *httpexpect.Array, expectRes bool) {
	for _, item := range data.Iter() {
		for _, table := range item.Object().Path("$.tables").Array().Iter() {
			if expectRes {
				table.Object().Path("$.columns").NotNull()
			} else {
				table.Object().Path("$.columns").IsNull()
			}
		}
	}
}
func GetSpecTable(resp *httpexpect.Response, tableName string) (string, string) {
	dbName := ""
	for _, db := range resp.JSON().Path("$.data").Array().Iter() {
		for _, table := range db.Path("$.tables").Array().Iter() {
			if table.Path("$.name").String().Raw() == tableName {
				dbName = db.Path("$.name").String().Raw()
				return dbName, tableName
			}
		}
	}
	return dbName, tableName
}

var (
	LastBillsEventTime time.Time
)

func SetLastBillsEventTime(specTime ...time.Time) {
	if len(specTime) != 0 {
		LastBillsEventTime = specTime[0]
	} else {
		LastBillsEventTime = time.Now()
	}
	l4g.Info(fmt.Sprintf("LastBillsEventTime has been set to : %s", LastBillsEventTime))
}

func SendClusterDeletedCost(w *ProductService.WebRequestInfo) {
	if time.Since(LastBillsEventTime).Minutes() > 1 {
		w.SendClusterCostQuantityAndPriceToCh(ProductService.ClusterDeletedEvent, decimal.NewFromFloat(float64(time.Since(LastBillsEventTime).Hours())))
	} else {
		w.SendClusterCostQuantityAndPriceToCh(ProductService.ClusterDeletedEvent, decimal.NewFromFloat(1).Div(decimal.NewFromFloat(60)))
	}
}
