package iam

import (
	"fmt"
	"net/http"
	"strings"

	l4g "github.com/alecthomas/log4go"
	"github.com/dailymotion/allure-go"
	"github.com/stretchr/testify/assert"
	platform "github.com/tidbcloud/qa-PlatformAPIAuto/webScene/cbp/platform"
	"github.com/tidbcloud/qa-gourd/ProductService"
	"github.com/tidbcloud/qa-gourd/handler"
	"github.com/tidbcloud/qa-gourd/util"
)

func RunShadowAccountSceneTest(w *ProductService.WebRequestInfo, UrlInfoMap map[string]ProductService.UrlInfo, reporter *util.AllureReporter) {
	at := &AccountTest{}
	at.Init(w, UrlInfoMap, reporter)
	pt := platform.InitPlatformTest(w, UrlInfoMap, reporter)
	pt.PrepareClusterDataScene()
	at.CheckShadowAccountScene()
}

func (at *AccountTest) CheckShadowAccountScene() {
	baseEmail := at.w.User.TenantEmail
	defer at.w.ResetRequestEmail(baseEmail)

	// reset token & check shadow user's privilege , then reset token to original owner user if ResetFlag is true
	checkPrivilege := func(ResetToken, SessionId string, expectCode int, ResetFlag bool) {
		at.w.SetToken(ResetToken)
		at.UseDBCheck(at.w.Cluster.ClusterId, SessionId, platform.Base_Database, expectCode)

		if ResetFlag {
			at.w.ResetRequestEmail(baseEmail)
		}
	}
	//currnet account check
	var curRole string

	at.Reporter.StepReport("CurrentAccountCheck",
		func() {
			curRole = at.GetCurrentUserRole()
		})

	// create session
	var originSessionId string
	pt := platform.InitPlatformTest(at.w, at.URLInfoMap, at.Reporter)
	at.Reporter.StepReport("Create Session",
		func() {
			originSessionId = pt.CreateSession()
		})
	//check self account get through
	at.Reporter.StepReport("CurShadowAccount",
		func() {
			var checkRes bool
			if curRole == "owner" || curRole == "member" {
				checkRes = at.CheckShadowAccountExistence(originSessionId, curRole, baseEmail, true)
			} else {
				checkRes = at.CheckShadowAccountExistence(originSessionId, curRole, baseEmail, false)
			}
			if !checkRes {
				at.w.Cluster.RemainFlag = true
				allure.FailNow(fmt.Errorf("current account shadow account check failed"))
			}
		})

	if curRole == "owner" {
		orgPathParam := map[string]string{
			"org_id": at.w.User.OrgId,
		}
		projectPathParam := map[string]string{
			"org_id":     at.w.User.OrgId,
			"project_id": at.w.User.ProjectId,
		}

		email := handler.OtherUserInfo.Chat2QueryUser
		var userId, InvitedSessionId, InvitedToken string
		at.Reporter.StepReport("InviteOwnerAndResetToken",
			func() {
				// invite user and reset email.
				at.w.DeleteUser(email)
				userId = at.InviteAndAccept(orgPathParam, email, "org:owner")
				at.w.ResetRequestEmail(email)
				InvitedSessionId = pt.CreateSession()
				if InvitedSessionId == "" {
					allure.FailNow(fmt.Errorf("create session or load session id failed"))
				}
				InvitedToken = at.w.Header["Authorization"]
			})

		//invite another owner
		at.Reporter.StepReport("OrgOwnerShadowAccount",
			func() {
				// check shadow user's existence
				// Please ensure that your session is used by the same user who created this session.
				at.w.ResetRequestEmail(baseEmail)
				checkRes := at.CheckShadowAccountExistence(originSessionId, "org:owner", email, true)
				// check shadow user's privilege
				checkPrivilege(InvitedToken, InvitedSessionId, 200, true)
				if !checkRes {
					at.w.Cluster.RemainFlag = true
					allure.FailNow(fmt.Errorf("OrgOwnerShadowAccount check failed"))
				}
			})
		//billing admin + no project owner shadow account
		at.Reporter.StepReport("OrgBillingAdminShadowAccount",
			func() {
				// check shadow user's existence
				at.ChangeUserRole(orgPathParam, userId, "org:billing_admin")
				checkRes := at.CheckShadowAccountExistence(originSessionId, "org:billing_admin", email, false)
				// check shadow user's privilege
				checkPrivilege(InvitedToken, InvitedSessionId, 403, true)
				if !checkRes {
					at.w.Cluster.RemainFlag = true
					allure.FailNow(fmt.Errorf("OrgBillingAdminShadowAccount check failed"))
				}
			})

		//member + project owner shadow account
		at.Reporter.StepReport("ProjectOwnerShadowAccount",
			func() {
				// check shadow user's existence
				at.InviteUserToJoin(projectPathParam, email, "project:owner")
				checkRes := at.CheckShadowAccountExistence(originSessionId, "project:owner", email, true)
				// check shadow user's privilege
				checkPrivilege(InvitedToken, InvitedSessionId, 200, false)
				if !checkRes {
					at.w.Cluster.RemainFlag = true
					allure.FailNow(fmt.Errorf("ProjectOwnerShadowAccount check failed"))
				}
				pt.CloseSession(InvitedSessionId)
				// after delete user , check its shadow account's existence.
				at.w.ResetRequestEmail(baseEmail)
				// delete user
				at.DeleteUserById(orgPathParam, userId)
				checkRes = at.CheckShadowAccountExistence(originSessionId, "delete member", email, false)
				if !checkRes {
					at.w.Cluster.RemainFlag = true
					allure.FailNow(fmt.Errorf("DeletedOrgUserShadowAccount check failed"))
				}
			})

	}
}

func (at *AccountTest) UseDBCheck(clusterId, sessionId, dbName string, statusCode int) {
	pathParam := map[string]string{
		"org_id":     at.w.User.OrgId,
		"project_id": at.w.User.ProjectId,
		"cluster_id": clusterId,
	}
	// use db
	if dbName == "" {
		dbName = fmt.Sprintf("%s_%s", dbPrefix, clusterId)
	}

	sql := fmt.Sprintf("use %s;", dbName)
	body := map[string]string{"sql": sql, "sessionid": sessionId}
	runSqlResp := at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap["executeSql"], body, pathParam, nil)
	at.Reporter.StepReport(fmt.Sprintf("Use DB Check, sql is : %s , status code is %d (should be %d) ,internal code is : %.0f.",
		sql,
		runSqlResp.Raw().StatusCode,
		statusCode,
		runSqlResp.JSON().Path("$.code").Number().Raw()),
		func() {
			runSqlResp.Status(statusCode)
		})
}

func (at *AccountTest) CheckShadowAccountExistence(sessionId, role, email string, expectFind bool) bool {
	dbo := &platform.DataAPIDBOperation{
		Pt:        platform.InitPlatformTest(at.w, at.URLInfoMap, at.Reporter),
		SessionId: sessionId,
	}
	var result bool

	sql := fmt.Sprintf("select User from mysql.user where plugin = 'tidb_auth_token' and User_attributes like '%%%s%%';", email)
	at.Reporter.StepReport(fmt.Sprintf("QueryUser(%s)(%s)InMysqlUserTable", email, role),
		func() {
			runSqlResp := dbo.ExecuteSqlAndGetResponse(sql)
			runSqlResp.Status(http.StatusOK)

			//shadow account will be created or deleted asynchronously, so we need to check it multiple times in 1 min.
			for round := 0; round <= 8; round += 1 {
				util.SecondTimeWait(2, 5, "Wait For Shadow Account Sync")
				runSqlResp = dbo.ExecuteSqlAndGetResponse(sql)
				l4g.Info("Get User %s( %s ), sql is : %s, status code is %d", email, role, sql, runSqlResp.Raw().StatusCode)
				runSqlResp.Status(http.StatusOK)

				find := false
				for _, user := range runSqlResp.JSON().Path(platform.DataRowsPath).Array().Iter() {
					find = true
					l4g.Info("Find user %s (%s) in mysql.user (%s), result is true.", email, role, user.Array().Value(0).String().Raw())
				}
				if round == 0 {
					result = find // Init result in first round
					continue
				}

				if find != result {
					// If the find result in current round is different from the previous result, break the loop.
					result = find
					break
				}
			}
		})
	at.Reporter.StepReport(fmt.Sprintf("Find user (%s)(%s) in mysql.user, the result is: %t, expect is : %t", email, role, result, expectFind),
		func() {
			assert.Equal(at.Reporter.T, expectFind, result)
		})
	return result == expectFind
}

func (at *AccountTest) GetThroughAccount(sessionId, role, email string, expectFind bool) {
	dbo := &platform.DataAPIDBOperation{
		Pt:        platform.InitPlatformTest(at.w, at.URLInfoMap, at.Reporter),
		SessionId: sessionId,
	}
	sql := "select * from mysql.user;"
	runSqlResp := dbo.ExecuteSqlAndGetResponse(sql)

	l4g.Info("Get User column's index, sql is : %s, status code is %d", sql, runSqlResp.Raw().StatusCode)
	runSqlResp.Status(http.StatusOK)

	index := 0
	for n, val := range runSqlResp.JSON().Path("$.data.columns").Array().Iter() {
		if val.Path("$.col").String().Raw() == "User" {
			index = n
			break
		}
	}
	//同步有滞后，确保1min内有数据
	prefix := email[:6]
	var result bool
	for round := 0; round <= 8; round += 1 {
		util.SecondTimeWait(2, 5, "Wait For Shadow Account Sync")
		runSqlResp = dbo.ExecuteSqlAndGetResponse(sql)
		l4g.Info("Get User %s( %s ), sql is : %s, status code is %d", email, role, sql, runSqlResp.Raw().StatusCode)
		runSqlResp.Status(http.StatusOK)

		find := false
		for _, val := range runSqlResp.JSON().Path(platform.DataRowsPath).Array().Iter() {
			user := strings.Split(val.Array().Element(index).String().Raw(), ".")
			if len(user) == 1 {
				continue
			}

			if len(user[1]) >= 6 && strings.HasPrefix(user[1], prefix) {
				find = true
				l4g.Info("Find user %s( %s ) in mysql.user", email, role)
				break
			} else if strings.HasPrefix(prefix, user[1]) {
				find = true
				l4g.Info("Find user %s( %s ) in mysql.user", email, role)
				break
			}
		}
		if round == 0 {
			result = find //初始化运行结果
			continue
		}

		if find != result {
			//本次和上次运行结果不同，则跳出循环
			result = find
			break
		}
	}
	at.Reporter.StepReport(fmt.Sprintf("Current role is %s.Find account in mysql.user, the result is: %t, expect is : %t", role, result, expectFind),
		func() {
			assert.Equal(at.Reporter.T, expectFind, result)
		})

}
