package iam

import (
	"database/sql"
	"fmt"
	"net/http"

	l4g "github.com/alecthomas/log4go"
	"github.com/duke-git/lancet/v2/slice"
	"github.com/tidbcloud/qa-PlatformAPIAuto/common"

	"github.com/dailymotion/allure-go"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/qa-gourd/ProductModel"
	"github.com/tidbcloud/qa-gourd/ProductService"
	"github.com/tidbcloud/qa-gourd/util"
)

func RunSQLUserTest(w *ProductService.WebRequestInfo, UrlInfoMap map[string]ProductService.UrlInfo, reporter *util.AllureReporter) {
	at := &AccountTest{}
	at.Init(w, UrlInfoMap, reporter)
	at.CheckSQLUserScene()
}
func (at *AccountTest) CheckSQLUserScene() {
	if at.w.Cluster.ClusterId == "" {
		l4g.Error("clusterId is null")
		return
	}

	tierType := "Serverless"
	at.Reporter.StepReport("Prepare-SetTrafficFilter", func() {
		if !at.w.Cluster.IsServerless {
			tierType = "Dedicated"
			at.w.AddTrafficFilter(ALLAllowedCIDR, false)
		}
	})

	// create sql user
	at.Reporter.StepReport(fmt.Sprintf("Test%sSQLUserScene", tierType), func() {
		// check cluster's ability to support feat SQL User
		// get serverless tier's prefix token
		at.Reporter.StepReport("A-CheckSQLUser'sAvailability", func() {
			tokenPrefix, err := at.GetTokenFromCluster()
			if err != nil {
				fmt.Println("GetTokenFromCluster Error:", err)
				allure.FailNow(err)
			}
			at.SetSQLUserInfo(tokenPrefix)
		})
		at.Reporter.StepReport("B-TestCreateSQLUser", func() {
			err := at.CreateSQLUser()
			if err != nil {
				allure.FailNow(err)
			}
			_, db, _ := at.CheckSQLUserConnect("mysql")
			_, err = util.QueryData(db, CreateCustomRoleSQL)
			if err != nil {
				allure.FailNow(err)
			}
		})
		at.Reporter.StepReport("C-TestEditSQLUser", func() {
			err := at.EditSQLUser()
			if err != nil {
				allure.FailNow(err)
			}
			at.CheckSQLUserConnect("test")
		})
		at.Reporter.StepReport("D-TestSQLUserPrivilege", func() {
			at.CheckSQLUserPrivilege()
		})
		at.Reporter.StepReport("E-TestDeleteSQLUser", func() {
			err := at.DeleteSQLUser()
			if err != nil {
				allure.FailNow(err)
			}
		})
		at.Reporter.StepReport("F-TestDeletedSQLUserExistence", func() {
			isExist, err := at.CheckSQLUserExistence()
			if isExist {
				allure.FailNow(err)
			}
		})
	})
}

func (at *AccountTest) CreateSQLUser() error {
	body := map[string]interface{}{
		"auth_method":  at.SQLUser.Auth_method,
		"builtin_role": at.SQLUser.Builtin_role,
		"custom_role":  []string{},
		"password":     at.SQLUser.Password,
		"user_name":    at.SQLUser.User_name,
	}
	createSQLUserResp, err := at.OperateSQLUser("createSQLUser", body)
	at.Reporter.StepReport(fmt.Sprintf("create SQL User %s, status code is : %v (should be %v)",
		at.SQLUser.User_name,
		createSQLUserResp.Raw().StatusCode,
		http.StatusOK,
	), func() {
		assert.Equal(at.Reporter.T, createSQLUserResp.Raw().StatusCode, http.StatusOK)
	})
	return err
}

func (at *AccountTest) EditSQLUser() error {
	at.SQLUser.Password = PasswordEdit
	body := map[string]interface{}{
		"builtin_role": at.SQLUser.Builtin_role,
		"custom_role":  at.SQLUser.Custom_role,
		"password":     at.SQLUser.Password,
		"user_name":    at.SQLUser.User_name,
	}
	editSQLUserResp, err := at.OperateSQLUser("editSQLUser", body)
	at.Reporter.StepReport(fmt.Sprintf("Edit SQL User %s, edit password to : %s , add custom_role : %+v , status code is : %v ",
		at.SQLUser.User_name,
		at.SQLUser.Password,
		at.SQLUser.Custom_role,
		editSQLUserResp.Raw().StatusCode,
	), func() {
		assert.Equal(at.Reporter.T, editSQLUserResp.Raw().StatusCode, http.StatusOK)
	})
	return err
}

func (at *AccountTest) DeleteSQLUser() error {
	body := map[string]interface{}{
		"user_name": at.SQLUser.User_name,
	}
	deleteSQLUserResp, err := at.OperateSQLUser("deleteSQLUser", body)
	at.Reporter.StepReport(fmt.Sprintf("Delete SQL User %s , status code is : %v ",
		at.SQLUser.User_name,
		deleteSQLUserResp.Raw().StatusCode,
	), func() {
		assert.Equal(at.Reporter.T, deleteSQLUserResp.Raw().StatusCode, http.StatusOK)
	})
	return err
}

func (at *AccountTest) ListSQLUser() (*SQLUserResp, error) {
	listSQLUserResp, _ := at.OperateSQLUser("listSQLUsers", nil)

	var SQLUserRespData SQLUserResp
	SQLUserRespData, err := util.UnmarshalResult(listSQLUserResp, SQLUserRespData)
	at.Reporter.StepReport(fmt.Sprintf("List SQL Users ,Total %v users  ,User list is : %v ",
		SQLUserRespData.Total,
		SQLUserRespData.Items,
	), func() {
		assert.Equal(at.Reporter.T, listSQLUserResp.Raw().StatusCode, http.StatusOK)
	})
	return &SQLUserRespData, err
}

func (at *AccountTest) CheckSQLUserPrivilege() (bool, error) {
	// check SQL User's existence
	isExist, err := at.CheckSQLUserExistence()
	at.Reporter.StepReport(fmt.Sprintf("Check existence of SQL User %s with builtin-role : %s , custom role : %v",
		at.SQLUser.User_name,
		at.SQLUser.Builtin_role,
		at.SQLUser.Custom_role,
	), func() {
		assert.Equal(at.Reporter.T, true, isExist)
	})
	if !isExist {
		return false, err
	}
	// check SQL User's privilege by show grants sentence
	isCorrect, err := at.CheckSQLUserRole()
	if !isCorrect {
		return false, err
	}
	return true, err
}

func (at *AccountTest) CheckSQLUserConnect(DBName string) (bool, *sql.DB, error) {
	//获取cluster信息
	ClusterInfo, err := at.w.GetClusterInfo()
	if err != nil {
		return false, nil, err
	}
	tidbAddress := *ClusterInfo.Cluster.Endpoint.Address
	tidbPort := int64(*ClusterInfo.Cluster.Endpoint.Port)

	var db *sql.DB
	db, err = at.w.Cluster.ConnectTidbUseTLS(tidbAddress, tidbPort, at.SQLUser.Password, DBName, at.SQLUser.User_name)
	at.Reporter.StepReport(fmt.Sprintf("Check SQL User %s's connect to tidb with password : %s , DBName : %s",
		at.SQLUser.User_name,
		at.SQLUser.Password,
		DBName,
	), func() {
		assert.Equal(at.Reporter.T, true, err == nil)
	})

	if err != nil {
		return false, db, err
	}
	return true, db, err
}

func (at *AccountTest) CheckSQLUserRole() (bool, error) {
	// select * from mysql.role_edges where to_user in ('user_name') ORDER BY 1
	showRoleSQL := fmt.Sprintf("select from_user from mysql.role_edges where to_user in ('%s')", at.SQLUser.User_name)
	showRoleDBName := "mysql"
	// get db
	_, db, err := at.CheckSQLUserConnect(showRoleDBName)
	if err != nil {
		return false, err
	}

	rolesRows, err := util.QueryData(db, showRoleSQL)
	if err != nil {
		return false, err
	}
	l4g.Info("SQL User %s's role_edges is : %v", at.SQLUser.User_name, rolesRows)

	// expectRows is the combination of builtin_role and custom_role.
	expectRows := []string{at.SQLUser.Builtin_role}
	expectRows = append(expectRows, at.SQLUser.Custom_role...)
	l4g.Info("SQL User %s's expect role_edges is : %v", at.SQLUser.User_name, expectRows)

	// check
	var res []string
	at.Reporter.StepReport(fmt.Sprintf("Check SQL User %s's actual role_edges : %v",
		at.SQLUser.User_name,
		rolesRows,
	), func() {
		res = slice.Difference(rolesRows, expectRows)
		assert.Equal(at.Reporter.T, true, len(res) == 0)
	})

	if len(res) == 0 {
		return true, nil
	} else {
		return false, fmt.Errorf("SQL User %s's role_edges is not correct , expect role_edges : %v , actual role_edges : %v",
			at.SQLUser.User_name,
			expectRows,
			rolesRows)
	}
}
func (at *AccountTest) GetTokenFromCluster() (string, error) {
	ClusterInfo, err := at.w.GetClusterInfo()
	if err != nil {
		return "", err
	}

	// serverless tier can support SQL User
	if *ClusterInfo.Cluster.DevTierType == ProductModel.CentralClusterDevTierClusterTypeDEVTIERSERVERLESS {
		at.Reporter.StepReport(fmt.Sprintf("Serverless support SQL User, token is : %s",
			*ClusterInfo.Cluster.Endpoint.Token,
		), func() {
		})
		return *ClusterInfo.Cluster.Endpoint.Token, nil
	}
	// dedicated tier must satisfy the following conditions :
	// 1. version > v6.6.0.
	// 2. Features : delegate user must be enabled.
	if ok, _ := common.CompareVersion(*ClusterInfo.Cluster.Version, ValidSQLUserClusterVersion); !ok {
		at.Reporter.StepReport(fmt.Sprintf("Dedicated Cluster's version must be greater than %s , now is %s",
			ValidSQLUserClusterVersion,
			*ClusterInfo.Cluster.Version,
		), func() {
			assert.Equal(at.Reporter.T, false, true)
		})
		return "", fmt.Errorf(fmt.Sprintf("Cluster version is : %s , can not meet the requirement of feature SQL User.", *ClusterInfo.Cluster.Version))
	}

	for _, feature := range ClusterInfo.Cluster.Features {
		if *feature == ProductModel.CentralClusterAvailableFeatureDELEGATEUSER {
			at.Reporter.StepReport(fmt.Sprintf("Cluster supports %s",
				ProductModel.CentralClusterAvailableFeatureDELEGATEUSER,
			), func() {
			})
			return "", nil
		}
	}
	at.Reporter.StepReport(fmt.Sprintf("Cluster doesn't support %s",
		ProductModel.CentralClusterAvailableFeatureDELEGATEUSER,
	), func() {
		assert.Equal(at.Reporter.T, false, true)
	})
	return "", fmt.Errorf("cluster can not meet the requirement of feature delegate user")
}
