package e2e

import (
	"encoding/json"
	"fmt"
	"strings"
)

type openApiListUsersRsp struct {
	Users []openApiUser `json:"users"`
}

type openApiUser struct {
	UserId        string                `json:"userId"`
	Email         string                `json:"email"`
	Status        string                `json:"status"`
	OrgRole       *openApiUserRoleData  `json:"orgRole,omitempty"`
	ProjectRoles  []openApiUserRoleData `json:"projectRoles,omitempty"`
	InstanceRoles []openApiUserRoleData `json:"instanceRoles,omitempty"`
}

type openApiUserRoleData struct {
	ScopeId  string `json:"scopeId"`
	RbacRole string `json:"rbacRole"`
}

func openApiListUserByEmail(email string) (*openApiUser, int, string) {
	url := DevIamOpenApiHost + fmt.Sprintf("/v1beta1/members?email=%s", email)
	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}
	code, data, err := RestyExec("GET", url, header, "")
	if err != nil {
		return nil, 0, err.Error()
	}
	if code != 200 {
		return nil, code, data
	}
	var rsp openApiListUsersRsp
	if json.Unmarshal([]byte(data), &rsp) != nil || len(rsp.Users) == 0 {
		return nil, code, data
	}
	return &rsp.Users[0], code, data
}

func openApiFindUserByIdAndEmail(userId, email string) (*openApiUser, int, string) {
	url := DevIamOpenApiHost + fmt.Sprintf("/v1beta1/members?email=%s", email)
	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}
	code, data, err := RestyExec("GET", url, header, "")
	if err != nil {
		return nil, 0, err.Error()
	}
	if code != 200 {
		return nil, code, data
	}
	var rsp openApiListUsersRsp
	if json.Unmarshal([]byte(data), &rsp) != nil {
		return nil, code, data
	}
	for i := range rsp.Users {
		if rsp.Users[i].UserId == userId {
			return &rsp.Users[i], code, data
		}
	}
	return nil, code, data
}

func ExecOpenApiUsersCase() {
	OpenApi_ListUsers()
	OpenApi_ListUsers_WithPageSize()
	OpenApi_ListUsers_WithStatusFilter()
	OpenApi_ListUsers_WithEmailFilter()
	OpenApi_ListUsers_WithRoleFilter()
	OpenApi_ListUsers_WithProjectFilter()
	OpenApi_ListUsers_WithOrderBy()

	OpenApi_InviteUsers_OrgRoleRequired()
	OpenApi_InviteUsers_InvalidEmail()
	OpenApi_InviteUsers_DuplicateProjectScopeId()
	OpenApi_InviteUsers_DuplicateInstanceScopeId()

	invitedUserId, invitedEmail := OpenApi_InviteUsers_Success()

	OpenApi_UpdateUser_NoOrgRoleKeepsExisting()
	OpenApi_UpdateUser_InvalidUserId()
	OpenApi_UpdateUser_DuplicateProjectScopeId()

	OpenApi_UpdateUser_Success(invitedUserId, invitedEmail)
	OpenApi_BatchUpdateUsers_WithRealUser(invitedUserId, invitedEmail)

	OpenApi_BatchUpdateUsers_Normal()
	OpenApi_BatchUpdateUsers_InvalidUserId()

	OpenApi_ResendInvitation_Success(invitedUserId, invitedEmail)

	OpenApi_DeleteUser_InvalidUserId()

	OpenApi_ResendInvitation_InvalidUserId()

	invitedWithClusterUserId, invitedWithClusterEmail := OpenApi_InviteUsers_WithInstanceRoles()
	OpenApi_UpdateUser_WithInstanceRoles(invitedWithClusterUserId, invitedWithClusterEmail)
}

func OpenApi_ListUsers() {
	url := DevIamOpenApiHost + "/v1beta1/members"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	code, data, err := RestyExec("GET", url, header, "")
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "ListUsers", CaseDetail{
		Url:       url,
		ReqHeader: header,
		Pass:      code == 200,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_ListUsers_WithPageSize() {
	url := DevIamOpenApiHost + "/v1beta1/members?pageSize=2"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	code, data, err := RestyExec("GET", url, header, "")
	if err != nil {
		code = 0
		data = err.Error()
	}

	pass := false
	if code == 200 {
		var rsp struct {
			Users []struct{} `json:"users"`
		}
		if json.Unmarshal([]byte(data), &rsp) == nil {
			pass = len(rsp.Users) <= 2
		}
	}

	SaveCase("OpenApi_Users", "ListUsers_WithPageSize", CaseDetail{
		Url:       url,
		ReqHeader: header,
		Pass:      pass,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_ListUsers_WithStatusFilter() {
	url := DevIamOpenApiHost + "/v1beta1/members?statuses=ACTIVE"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	code, data, err := RestyExec("GET", url, header, "")
	if err != nil {
		code = 0
		data = err.Error()
	}

	pass := false
	var mismatchStatuses []string
	if code == 200 {
		var rsp openApiListUsersRsp
		if json.Unmarshal([]byte(data), &rsp) == nil {
			for _, u := range rsp.Users {
				if !strings.EqualFold(u.Status, "ACTIVE") {
					mismatchStatuses = append(mismatchStatuses, u.Email+":"+u.Status)
				}
			}
			pass = len(rsp.Users) > 0
		}
	}

	extra := map[string]interface{}{}
	if len(mismatchStatuses) > 0 {
		extra["mismatch_statuses"] = mismatchStatuses
	}

	SaveCase("OpenApi_Users", "ListUsers_WithStatusFilter", CaseDetail{
		Url:       url,
		ReqHeader: header,
		Pass:      pass,
		RspCode:   code,
		RspData:   data,
		Extra:     extra,
	})
}

func OpenApi_InviteUsers_OrgRoleRequired() {
	url := DevIamOpenApiHost + "/v1beta1/members"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := `{
		"emails": ["e2e-test@example.com"]
	}`

	code, data, err := RestyExec("POST", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "InviteUsers_OrgRoleRequired", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      code == 400,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_InviteUsers_InvalidEmail() {
	url := DevIamOpenApiHost + "/v1beta1/members"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := `{
		"emails": []
	}`

	code, data, err := RestyExec("POST", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "InviteUsers_EmptyEmails", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      code == 400,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_InviteUsers_DuplicateProjectScopeId() {
	url := DevIamOpenApiHost + "/v1beta1/members"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := fmt.Sprintf(`{
		"emails": ["e2e-invite-dup-%s@example.com"],
		"orgRole": {"scopeId": "1440002", "rbacRole": "org:member"},
		"projectRoles": [
			{"scopeId": "3199935", "rbacRole": "project:dev"},
			{"scopeId": "3199935", "rbacRole": "project:readonly"}
		]
	}`, CurTimeStr())

	code, data, err := RestyExec("POST", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "InviteUsers_DuplicateProjectScopeId", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      code == 400,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_InviteUsers_DuplicateInstanceScopeId() {
	url := DevIamOpenApiHost + "/v1beta1/members"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := fmt.Sprintf(`{
		"emails": ["e2e-invite-dup-inst-%s@example.com"],
		"orgRole": {"scopeId": "1440002", "rbacRole": "org:member"},
		"instanceRoles": [
			{"scopeId": "%d", "rbacRole": "cluster:admin"},
			{"scopeId": "%d", "rbacRole": "cluster:viewer"}
		]
	}`, CurTimeStr(), clusterId, clusterId)

	code, data, err := RestyExec("POST", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "InviteUsers_DuplicateInstanceScopeId", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      code == 400,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_UpdateUser_NoOrgRoleKeepsExisting() {
	url := DevIamOpenApiHost + "/v1beta1/members/999999999"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := `{}`

	code, data, err := RestyExec("PATCH", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "UpdateUser_NoOrgRoleKeepsExisting", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      code != 400 || !strings.Contains(data, "orgRole is required"),
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_UpdateUser_InvalidUserId() {
	url := DevIamOpenApiHost + "/v1beta1/members/invalid"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := `{}`

	code, data, err := RestyExec("PATCH", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "UpdateUser_InvalidUserId", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      code == 400 || code == 403,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_UpdateUser_DuplicateProjectScopeId() {
	url := DevIamOpenApiHost + "/v1beta1/members/999999999"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := `{
		"projectRoles": [
			{"scopeId": "3199935", "rbacRole": "project:dev"},
			{"scopeId": "3199935", "rbacRole": "project:readonly"}
		]
	}`

	code, data, err := RestyExec("PATCH", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "UpdateUser_DuplicateProjectScopeId", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      code == 400,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_BatchUpdateUsers_Normal() {
	url := DevIamOpenApiHost + "/v1beta1/members/batchUpdate"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := `{
		"users": [{
			"userId": "999999999",
			"orgRole": {"scopeId": "1440002", "rbacRole": "org:member"}
		}]
	}`

	code, data, err := RestyExec("POST", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "BatchUpdateUsers_Normal", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      code == 200,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_BatchUpdateUsers_InvalidUserId() {
	url := DevIamOpenApiHost + "/v1beta1/members/batchUpdate"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := `{
		"users": [{
			"userId": "invalid",
			"orgRole": {"scopeId": "1440002", "rbacRole": "org:member"}
		}]
	}`

	code, data, err := RestyExec("POST", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	pass := false
	if code == 200 {
		var rsp struct {
			Results []struct {
				UserId  string `json:"userId"`
				Success bool   `json:"success"`
				Error   *struct {
					Code string `json:"code"`
				} `json:"error,omitempty"`
			} `json:"results"`
		}
		if json.Unmarshal([]byte(data), &rsp) == nil && len(rsp.Results) == 1 {
			pass = !rsp.Results[0].Success && rsp.Results[0].Error != nil && rsp.Results[0].Error.Code == "INVALID_ARGUMENT"
		}
	}

	SaveCase("OpenApi_Users", "BatchUpdateUsers_InvalidUserId", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      pass,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_DeleteUser_InvalidUserId() {
	url := DevIamOpenApiHost + "/v1beta1/members/invalid"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	code, data, err := RestyExec("DELETE", url, header, "")
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "DeleteUser_InvalidUserId", CaseDetail{
		Url:       url,
		ReqHeader: header,
		Pass:      code == 400 || code == 403,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_ResendInvitation_InvalidUserId() {
	url := DevIamOpenApiHost + "/v1beta1/members/invalid/resendInvitation"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	code, data, err := RestyExec("POST", url, header, "")
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "ResendInvitation_InvalidUserId", CaseDetail{
		Url:       url,
		ReqHeader: header,
		Pass:      code == 400 || code == 403,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_ListUsers_WithEmailFilter() {
	url := DevIamOpenApiHost + "/v1beta1/members?email=pingcap"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	code, data, err := RestyExec("GET", url, header, "")
	if err != nil {
		code = 0
		data = err.Error()
	}

	pass := false
	if code == 200 {
		var rsp struct {
			Users []struct {
				Email string `json:"email"`
			} `json:"users"`
		}
		if json.Unmarshal([]byte(data), &rsp) == nil {
			pass = true
			for _, u := range rsp.Users {
				if !strings.Contains(strings.ToLower(u.Email), "pingcap") {
					pass = false
					break
				}
			}
		}
	}

	SaveCase("OpenApi_Users", "ListUsers_WithEmailFilter", CaseDetail{
		Url:       url,
		ReqHeader: header,
		Pass:      pass,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_ListUsers_WithRoleFilter() {
	url := DevIamOpenApiHost + "/v1beta1/members?roles=org:owner"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	code, data, err := RestyExec("GET", url, header, "")
	if err != nil {
		code = 0
		data = err.Error()
	}

	pass := false
	if code == 200 {
		var rsp struct {
			Users []struct {
				OrgRole *struct {
					RbacRole string `json:"rbacRole"`
				} `json:"orgRole"`
			} `json:"users"`
		}
		if json.Unmarshal([]byte(data), &rsp) == nil {
			pass = true
			for _, u := range rsp.Users {
				if u.OrgRole == nil || u.OrgRole.RbacRole != "org:owner" {
					pass = false
					break
				}
			}
		}
	}

	SaveCase("OpenApi_Users", "ListUsers_WithRoleFilter", CaseDetail{
		Url:       url,
		ReqHeader: header,
		Pass:      pass,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_ListUsers_WithProjectFilter() {
	url := DevIamOpenApiHost + "/v1beta1/members?projectId=3199935"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	code, data, err := RestyExec("GET", url, header, "")
	if err != nil {
		code = 0
		data = err.Error()
	}

	pass := false
	if code == 200 {
		var rsp openApiListUsersRsp
		if json.Unmarshal([]byte(data), &rsp) == nil {
			pass = len(rsp.Users) > 0
		}
	}

	SaveCase("OpenApi_Users", "ListUsers_WithProjectFilter", CaseDetail{
		Url:       url,
		ReqHeader: header,
		Pass:      pass,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_ListUsers_WithOrderBy() {
	url := DevIamOpenApiHost + "/v1beta1/members?orderBy=createTime&pageSize=5"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	code, data, err := RestyExec("GET", url, header, "")
	if err != nil {
		code = 0
		data = err.Error()
	}

	SaveCase("OpenApi_Users", "ListUsers_WithOrderBy", CaseDetail{
		Url:       url,
		ReqHeader: header,
		Pass:      code == 200,
		RspCode:   code,
		RspData:   data,
	})
}

func OpenApi_InviteUsers_Success() (string, string) {
	url := DevIamOpenApiHost + "/v1beta1/members"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	email := fmt.Sprintf("e2e-invite-%s@example.com", CurTimeStr())
	body := fmt.Sprintf(`{
		"emails": ["%s"],
		"orgRole": {"rbacRole": "org:member"},
		"projectRoles": [
			{"scopeId": "3199935", "rbacRole": "project:readonly"}
		]
	}`, email)

	code, data, err := RestyExec("POST", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	var invitedUserId string
	invitePass := false
	if code == 200 {
		var rsp struct {
			Users []struct {
				UserId string `json:"userId"`
				Email  string `json:"email"`
			} `json:"users"`
			Success bool   `json:"success"`
			Message string `json:"message"`
		}
		if json.Unmarshal([]byte(data), &rsp) == nil && rsp.Success && len(rsp.Users) == 1 {
			invitedUserId = rsp.Users[0].UserId
			invitePass = rsp.Users[0].Email == email
		}
	}

	verifyPass := false
	var verifyCode int
	var verifyData string
	if invitePass && invitedUserId != "" {
		u, vCode, vData := openApiListUserByEmail(email)
		verifyCode = vCode
		verifyData = vData
		if u != nil &&
			u.Email == email &&
			strings.EqualFold(u.Status, "PENDING") &&
			u.OrgRole != nil && u.OrgRole.RbacRole == "org:member" {
			verifyPass = true
		}
	}

	SaveCase("OpenApi_Users", "InviteUsers_Success", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      invitePass && verifyPass,
		RspCode:   code,
		RspData:   data,
		Extra: map[string]interface{}{
			"invite_pass": invitePass,
			"verify_pass": verifyPass,
			"verify_code": verifyCode,
			"verify_data": verifyData,
		},
	})

	return invitedUserId, email
}

func OpenApi_UpdateUser_Success(userId, email string) {
	if userId == "" {
		SaveCase("OpenApi_Users", "UpdateUser_Success", CaseDetail{
			Pass:    false,
			RspData: "skipped: no invited userId from InviteUsers_Success",
		})
		return
	}

	url := DevIamOpenApiHost + fmt.Sprintf("/v1beta1/members/%s", userId)

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := `{
		"orgRole": {"rbacRole": "org:billing_viewer"},
		"projectRoles": [
			{"scopeId": "3199935", "rbacRole": "project:dev"}
		]
	}`

	code, data, err := RestyExec("PATCH", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	updatePass := code == 200

	verifyPass := false
	var verifyCode int
	var verifyData string
	if updatePass {
		u, vCode, vData := openApiFindUserByIdAndEmail(userId, email)
		verifyCode = vCode
		verifyData = vData
		if u != nil &&
			u.OrgRole != nil && u.OrgRole.RbacRole == "org:billing_viewer" {
			verifyPass = true
		}
	}

	SaveCase("OpenApi_Users", "UpdateUser_Success", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      updatePass && verifyPass,
		RspCode:   code,
		RspData:   data,
		Extra: map[string]interface{}{
			"update_pass": updatePass,
			"verify_pass": verifyPass,
			"verify_code": verifyCode,
			"verify_data": verifyData,
		},
	})
}

func OpenApi_BatchUpdateUsers_WithRealUser(userId, email string) {
	if userId == "" {
		SaveCase("OpenApi_Users", "BatchUpdateUsers_WithRealUser", CaseDetail{
			Pass:    false,
			RspData: "skipped: no invited UserId from InviteUsers_Success",
		})
		return
	}

	url := DevIamOpenApiHost + "/v1beta1/members/batchUpdate"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := fmt.Sprintf(`{
		"users": [
			{
				"userId": "%s",
				"orgRole": {"rbacRole": "org:member"},
				"projectRoles": [
					{"scopeId": "3199935", "rbacRole": "project:readonly"}
				]
			},
			{
				"userId": "invalid_user_id",
				"orgRole": {"rbacRole": "org:member"}
			}
		]
	}`, userId)

	code, data, err := RestyExec("POST", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	batchPass := false
	if code == 200 {
		var rsp struct {
			Results []struct {
				UserId  string `json:"userId"`
				Success bool   `json:"success"`
				Error   *struct {
					Code string `json:"code"`
				} `json:"error,omitempty"`
			} `json:"results"`
		}
		if json.Unmarshal([]byte(data), &rsp) == nil && len(rsp.Results) == 2 {
			realOk := rsp.Results[0].UserId == userId && rsp.Results[0].Success
			invalidFail := rsp.Results[1].UserId == "invalid_user_id" && !rsp.Results[1].Success && rsp.Results[1].Error.Code == "INVALID_ARGUMENT"
			batchPass = realOk && invalidFail
		}
	}

	verifyPass := false
	var verifyCode int
	var verifyData string
	if batchPass {
		u, vCode, vData := openApiFindUserByIdAndEmail(userId, email)
		verifyCode = vCode
		verifyData = vData
		if u != nil &&
			u.OrgRole != nil && u.OrgRole.RbacRole == "org:member" {
			verifyPass = true
		}
	}

	SaveCase("OpenApi_Users", "BatchUpdateUsers_WithRealUser", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      batchPass && verifyPass,
		RspCode:   code,
		RspData:   data,
		Extra: map[string]interface{}{
			"batch_pass":  batchPass,
			"verify_pass": verifyPass,
			"verify_code": verifyCode,
			"verify_data": verifyData,
		},
	})
}

func OpenApi_ResendInvitation_Success(userId, email string) {
	if userId == "" {
		SaveCase("OpenApi_Users", "ResendInvitation_Success", CaseDetail{
			Pass:    false,
			RspData: "skipped: no invited UserId from InviteUsers_Success",
		})
		return
	}

	url := DevIamOpenApiHost + fmt.Sprintf("/v1beta1/members/%s/resendInvitation", userId)

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	code, data, err := RestyExec("POST", url, header, "")
	if err != nil {
		code = 0
		data = err.Error()
	}

	resendPass := code == 200

	verifyPass := false
	var verifyCode int
	var verifyData string
	if resendPass {
		u, vCode, vData := openApiFindUserByIdAndEmail(userId, email)
		verifyCode = vCode
		verifyData = vData
		if u != nil && u.UserId == userId && strings.EqualFold(u.Status, "PENDING") {
			verifyPass = true
		}
	}

	SaveCase("OpenApi_Users", "ResendInvitation_Success", CaseDetail{
		Url:       url,
		ReqHeader: header,
		Pass:      resendPass && verifyPass,
		RspCode:   code,
		RspData:   data,
		Extra: map[string]interface{}{
			"resend_pass": resendPass,
			"verify_pass": verifyPass,
			"verify_code": verifyCode,
			"verify_data": verifyData,
		},
	})
}

func OpenApi_InviteUsers_WithInstanceRoles() (string, string) {
	url := DevIamOpenApiHost + "/v1beta1/members"

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	email := fmt.Sprintf("e2e-invite-cluster-%s@example.com", CurTimeStr())
	body := fmt.Sprintf(`{
		"emails": ["%s"],
		"orgRole": {"rbacRole": "org:member"},
		"projectRoles": [
			{"scopeId": "3199935", "rbacRole": "project:dev"}
		],
		"instanceRoles": [
			{"scopeId": "%d", "rbacRole": "cluster:admin"}
		]
	}`, email, clusterId)

	code, data, err := RestyExec("POST", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	var invitedUserId string
	invitePass := false
	if code == 200 {
		var rsp struct {
			Users []struct {
				UserId string `json:"userId"`
				Email  string `json:"email"`
			} `json:"users"`
			Success bool   `json:"success"`
			Message string `json:"message"`
		}
		if json.Unmarshal([]byte(data), &rsp) == nil && rsp.Success && len(rsp.Users) == 1 {
			invitedUserId = rsp.Users[0].UserId
			invitePass = rsp.Users[0].Email == email
		}
	}

	verifyPass := false
	var verifyCode int
	var verifyData string
	if invitePass && invitedUserId != "" {
		u, vCode, vData := openApiListUserByEmail(email)
		verifyCode = vCode
		verifyData = vData
		if u != nil &&
			u.Email == email &&
			u.OrgRole != nil && u.OrgRole.RbacRole == "org:member" &&
			len(u.ProjectRoles) >= 1 && u.ProjectRoles[0].RbacRole == "project:dev" &&
			len(u.InstanceRoles) >= 1 {
			hasClusterAdmin := false
			for _, ir := range u.InstanceRoles {
				if ir.ScopeId == fmt.Sprintf("%d", clusterId) && ir.RbacRole == "cluster:admin" {
					hasClusterAdmin = true
					break
				}
			}
			if hasClusterAdmin {
				verifyPass = true
			}
		}
	}

	SaveCase("OpenApi_Users", "InviteUsers_WithInstanceRoles", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      invitePass && verifyPass,
		RspCode:   code,
		RspData:   data,
		Extra: map[string]interface{}{
			"invite_pass": invitePass,
			"verify_pass": verifyPass,
			"verify_code": verifyCode,
			"verify_data": verifyData,
		},
	})

	return invitedUserId, email
}

func OpenApi_UpdateUser_WithInstanceRoles(userId, email string) {
	if userId == "" {
		SaveCase("OpenApi_Users", "UpdateUser_WithInstanceRoles", CaseDetail{
			Pass:    false,
			RspData: "skipped: no invited userId from InviteUsers_WithInstanceRoles",
		})
		return
	}

	url := DevIamOpenApiHost + fmt.Sprintf("/v1beta1/members/%s", userId)

	header := map[string]string{
		"Content-type":       "application/json",
		DigestAuthUserHeader: orgOwnerKey,
	}

	body := fmt.Sprintf(`{
		"orgRole": {"rbacRole": "org:billing_viewer"},
		"projectRoles": [
			{"scopeId": "3199935", "rbacRole": "project:readonly"}
		],
		"instanceRoles": [
			{"scopeId": "%d", "rbacRole": "cluster:viewer"}
		]
	}`, clusterId)

	code, data, err := RestyExec("PATCH", url, header, body)
	if err != nil {
		code = 0
		data = err.Error()
	}

	updatePass := code == 200

	verifyPass := false
	var verifyCode int
	var verifyData string
	if updatePass {
		u, vCode, vData := openApiFindUserByIdAndEmail(userId, email)
		verifyCode = vCode
		verifyData = vData
		if u != nil &&
			u.OrgRole != nil && u.OrgRole.RbacRole == "org:billing_viewer" {
			hasClusterViewer := false
			for _, ir := range u.InstanceRoles {
				if ir.ScopeId == fmt.Sprintf("%d", clusterId) && ir.RbacRole == "cluster:viewer" {
					hasClusterViewer = true
					break
				}
			}
			if hasClusterViewer {
				verifyPass = true
			}
		}
	}

	SaveCase("OpenApi_Users", "UpdateUser_WithInstanceRoles", CaseDetail{
		Url:       url,
		ReqHeader: header,
		ReqBody:   body,
		Pass:      updatePass && verifyPass,
		RspCode:   code,
		RspData:   data,
		Extra: map[string]interface{}{
			"update_pass": updatePass,
			"verify_pass": verifyPass,
			"verify_code": verifyCode,
			"verify_data": verifyData,
		},
	})
}
