package iam

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

	l4g "github.com/alecthomas/log4go"
	"github.com/dailymotion/allure-go"
	"github.com/gavv/httpexpect/v2"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/qa-PlatformAPIAuto/common"
	"github.com/tidbcloud/qa-gourd/ProductModel"
	"github.com/tidbcloud/qa-gourd/ProductService"
	"github.com/tidbcloud/qa-gourd/handler"
	"github.com/tidbcloud/qa-gourd/util"
)

func RunInviteTest(w *ProductService.WebRequestInfo, UrlInfoMap map[string]ProductService.UrlInfo, reporter *util.AllureReporter) {
	at := &AccountTest{}
	at.Init(w, UrlInfoMap, reporter)
	at.CheckInviteScene()
}

func (at *AccountTest) CheckInviteScene() {
	at.Reporter.StepReport("A-InitializeInviteUserInfo", func() {
		at.SetDBaaSUserInfo(handler.OtherUserInfo.VisitorUser)
	})

	at.Reporter.StepReport("B-TestInviteToOrganization", func() {
		err := at.InviteUserToJoin(at.OrgPathParam, at.DBaaSUserInfo.UserEmail, at.DBaaSUserInfo.UserRole["org_role"])
		if err != nil {
			allure.FailNow(err)
		}
		util.SecondTimeWait(8, 15, "Wait For Invitation Email")
		at.UserAcceptInvitation(at.DBaaSUserInfo.UserEmail, at.OrgPathParam)
		at.DBaaSUserInfo.UserId, err = at.CheckUserExistence(at.OrgPathParam, at.DBaaSUserInfo.UserEmail,
			map[string]string{
				"rbac_role": UserInitialOrgRole,
				"status":    "ACTIVE",
			})
		if err != nil {
			allure.FailNow(err)
		}
	})
	at.Reporter.StepReport("C-TestInviteToProject", func() {
		err := at.InviteUserToJoin(at.ProjectPathParam, at.DBaaSUserInfo.UserEmail, at.DBaaSUserInfo.UserRole["project_role"])
		if err != nil {
			allure.FailNow(err)
		}
		_, err = at.CheckUserExistence(at.ProjectPathParam, at.DBaaSUserInfo.UserEmail,
			map[string]string{
				"rbac_role": UserInitialProjectRole,
				"status":    "ACTIVE",
			})
		if err != nil {
			allure.FailNow(err)
		}
	})

	at.Reporter.StepReport("D-TestAdjustUserRole", func() {
		at.ChangeUserRole(at.OrgPathParam, at.DBaaSUserInfo.UserId, UserAdjustOrgRole)
		_, err := at.CheckUserExistence(at.OrgPathParam, at.DBaaSUserInfo.UserEmail,
			map[string]string{
				"rbac_role": UserAdjustOrgRole,
			})
		if err != nil {
			allure.FailNow(err)
		}
		at.DBaaSUserInfo.UserRole["org_role"] = UserAdjustOrgRole

		at.ChangeUserRole(at.ProjectPathParam, at.DBaaSUserInfo.UserId, UserAdjustProjectRole)
		_, err = at.CheckUserExistence(at.ProjectPathParam, at.DBaaSUserInfo.UserEmail,
			map[string]string{
				"rbac_role": UserAdjustProjectRole,
			})
		if err != nil {
			allure.FailNow(err)
		}
		at.DBaaSUserInfo.UserRole["project_role"] = UserAdjustProjectRole
	})

	at.Reporter.StepReport("E-TestRemoveUserFromProject", func() {
		at.DeleteUserById(at.ProjectPathParam, at.DBaaSUserInfo.UserId)
	})
	at.Reporter.StepReport("F-TestRemoveUserFromOrganization", func() {
		at.DeleteUserById(at.OrgPathParam, at.DBaaSUserInfo.UserId)
	})
	at.Reporter.StepReport("G-TestRemovedUserExistence", func() {
		UserID, _ := at.CheckUserExistence(at.OrgPathParam, at.DBaaSUserInfo.UserEmail,
			map[string]string{
				"status": "ACTIVE",
			})
		at.Reporter.StepReport(fmt.Sprintf("Find removed user in org , expect user id is empty ,actual user id is : %s", UserID), func() {
			assert.Equal(at.Reporter.T, "", UserID)
		})
	})
}

// invite user to org / project , use {org_id : x, project_id : y} or {org_id : x} to determine to join org or project.
func (at *AccountTest) InviteUserToJoin(pathParam map[string]string, email, rbac_role string) error {
	if email == "" || rbac_role == "" || pathParam == nil {
		return fmt.Errorf(fmt.Sprintf("Wrong input params , email : %s, role : %s, pathParam : %+v", email, rbac_role, pathParam))
	}
	user := common.NewInviteUser(email, rbac_role)

	var url_name string
	if _, ok := pathParam["project_id"]; ok {
		url_name = "inviteUserToProject"
	} else {
		url_name = "inviteUserToOrg"
	}
	inviteUserResp := at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap[url_name], user, pathParam, nil)
	at.Reporter.StepReport(fmt.Sprintf(" %s, body : %+v, pathParam : %s , inviteUserResp : %v", url_name, user, pathParam, inviteUserResp.Body().Raw()),
		func() {
			assert.Equal(at.Reporter.T, http.StatusOK, inviteUserResp.Raw().StatusCode)
		})

	if inviteUserResp.Raw().StatusCode != http.StatusOK {
		return fmt.Errorf("%s failed , body : %+v, path param is : %+v , resp is : %v", url_name, user, pathParam, inviteUserResp.Raw())
	}
	return nil
}

// user accept invitation
func (at *AccountTest) UserAcceptInvitation(email string, orgPathParam map[string]string) {

	var auth0UserResp *httpexpect.Response
	for retry_round := 0; retry_round < 4; retry_round++ {
		// get invitation email url
		snapmailUrl := []string{"https://www.snapmail.cc", fmt.Sprintf("/emailList/%s", email)}
		getSnapmailResp := common.Get(at.Reporter.T, snapmailUrl, nil, nil, nil)
		emailHtml := getSnapmailResp.JSON().Path("$[0].html").String().Raw()
		urlRegexp := regexp.MustCompile(`http://url8461.tidbcloud.com/ls/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`)
		emailList := urlRegexp.FindStringSubmatch(emailHtml)
		urlList := strings.Split(emailList[0], "=")
		getEmailToken := common.Get(at.Reporter.T, []string{"https://url8461.tidbcloud.com", "/ls/click"}, nil, nil, map[string]string{"upn": urlList[1]})
		l4g.Info("getEmailToken %s", getEmailToken)

		// get token
		data := getEmailToken.Body().Raw()
		list := strings.Split(data, "token=")
		tokenList := strings.Split(list[1], "\"")
		token := tokenList[0]
		l4g.Info("token is %s", token)

		//注册用户
		auth0User := ProductModel.NewAuth0User(handler.OtherUserInfo.LoginPassword, token)
		auth0UserResp = at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap["auth0User"], auth0User, orgPathParam, nil)
		if auth0UserResp.Raw().StatusCode == http.StatusOK {
			break
		}

		util.SecondTimeWait(3, 10, "Retry Set Auth0 User")
	}

	at.Reporter.StepReport(fmt.Sprintf("auth user regist, auth0UserResp:%s", auth0UserResp.JSON().Raw()),
		func() {
			auth0UserResp.Status(http.StatusOK)
		})
	// get visitor token
	header := map[string]string{"referer": handler.AuthInfo.Refer,
		"content-type":         "application/json",
		realHeaderUserAgentKey: realHeaderUserAgentVal,
		realHeaderSecKey:       realHeaderSecVal,
		realHeaderPlatformKey:  realHeaderPlatformVal,
		realHeaderModelKey:     realHeaderModelVal,
		realHeaderSiteKey:      realHeaderSiteVal}

	// authenticate user
	authenticate := ProductModel.NewAuthenticate(handler.AuthInfo.ClientId,
		email, handler.OtherUserInfo.LoginPassword)
	authUrl := []string{"https://" + handler.AuthInfo.Auth0Authen, at.URLInfoMap["visitAuthenticate"].UrlPath}
	authenticateResp := util.SendRequest(at.Reporter.T, authUrl, at.URLInfoMap["visitAuthenticate"].UrlMethod, authenticate, nil, nil, header)
	cookie := common.GetCookieFromResp(authenticateResp)
	l4g.Info("header:%s", cookie)
	loginTicket := authenticateResp.JSON().Path("$.login_ticket").String().Raw()
	l4g.Info("loginTicket is %s", loginTicket)
	fmt.Println(loginTicket)

	// get authorize token
	authorize := ProductModel.NewAuthorize(handler.AuthInfo.ClientId, handler.AuthInfo.RedirectURI, handler.AuthInfo.State, handler.AuthInfo.Nonce, loginTicket, handler.AuthInfo.Auth0Client)

	auth0Config := httpexpect.Config{
		BaseURL:  fmt.Sprintf("https://%s", handler.AuthInfo.Auth0),
		Reporter: httpexpect.NewAssertReporter(at.Reporter.T),
	}
	visitAuthorizeReq := httpexpect.NewRequestC(auth0Config, "GET", at.URLInfoMap["visitAuthorize"].UrlPath)
	visitAuthorizeReq.WithQueryObject(authorize)
	visitAuthorizeReq.WithHeaders(cookie)
	visitAuthorizeReq.WithRedirectPolicy(httpexpect.DontFollowRedirects)
	visitAuthorizeResp := visitAuthorizeReq.Expect()
	l4g.Info("visitAuthorize , the body is %s, param is %s, visitAuthorizeResp is %+v", nil,
		authorize, visitAuthorizeResp.Text().Raw())
	fmt.Println(visitAuthorizeResp.Raw())
	data := visitAuthorizeResp.Text().Raw()
	list := strings.Split(data, "id_token=")
	visitToken := list[1]
	fmt.Println(visitToken)
	header = map[string]string{"authorization": "Bearer " + visitToken}

	// accept the invitation
	config := httpexpect.Config{
		BaseURL:  at.w.BaseUrl,
		Reporter: httpexpect.NewAssertReporter(at.Reporter.T),
	}
	acception := ProductModel.NewAcception(email, at.w.User.OrgId)
	acceptionReq := httpexpect.NewRequestC(config, "POST", at.URLInfoMap["acceptVisit"].UrlPath)
	acceptionReq.WithJSON(acception)
	acceptionReq.WithHeaders(header)
	acceptionReq.WithPathObject(orgPathParam)
	acceptionResp := acceptionReq.Expect()
	l4g.Info("accept the visition , the body is %s, pathParam is %s, acceptionResp is %+v", acception,
		orgPathParam, acceptionResp.JSON().Raw())
	at.Reporter.StepReport(fmt.Sprintf("accept the visition, acceptionResp:%s", acceptionResp.JSON().Raw()),
		func() {
			acceptionResp.Status(http.StatusOK)
		})

}

// adjust user's role in org/project,and use {org_id : x, project_id : y} or {org_id : x} to determine adjust user's org or project role.
func (at *AccountTest) ChangeUserRole(pathParam map[string]string, userId, newRole string) {
	var url_name string
	if _, ok := pathParam["project_id"]; ok {
		url_name = "changeProjectUserRole"
	} else {
		url_name = "changeOrgUserRole"
	}

	UserPathParam := make(map[string]string)
	for key, value := range pathParam {
		UserPathParam[key] = value
	}
	UserPathParam["user_id"] = userId
	body := map[string]string{"rbac_role": newRole}
	resp := at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap[url_name], body, UserPathParam, nil)
	at.Reporter.StepReport(fmt.Sprintf("%s , resp is : %s", url_name, resp.Body().Raw()), func() {
		assert.Equal(at.Reporter.T, http.StatusOK, resp.Raw().StatusCode)
	})
}

// remove user by userid from org/project, and use {org_id : x, project_id : y} or {org_id : x} to determine delete from org or project
func (at *AccountTest) DeleteUserById(pathParam map[string]string, userId string) {
	var url_name string
	if _, ok := pathParam["project_id"]; ok {
		url_name = "deleteProjectUser"
	} else {
		url_name = "deleteOrgUser"
	}
	UserPathParam := make(map[string]string)
	for key, value := range pathParam {
		UserPathParam[key] = value
	}
	UserPathParam["user_id"] = userId

	deleteInviteUserResp := at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap[url_name], nil, UserPathParam, nil)
	at.Reporter.StepReport(fmt.Sprintf("%s , deleteInviteUserResp is : %s", url_name, deleteInviteUserResp.Body().Raw()), func() {
		assert.Equal(at.Reporter.T, http.StatusOK, deleteInviteUserResp.Raw().StatusCode)
	})
}

// remove user by email from org/project, and use {org_id : x, project_id : y} or {org_id : x} to determine delete from org or project
func (at *AccountTest) DeleteUserByEmail(pathParam map[string]string, email string) {
	queryParam := map[string]string{
		"page":     "1",
		"per_page": "1000",
	}
	getUserAccessListResp := at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap["getOrgUserAccessList"], nil, pathParam, queryParam)
	userId := common.GetInviteAccessId(getUserAccessListResp, email)

	if userId == "000000" {
		return
	}
	// delete user if userId exists.
	var url_name string
	if _, ok := pathParam["project_id"]; ok {
		url_name = "deleteProjectUser"
	} else {
		url_name = "deleteOrgUser"
	}
	UserPathParam := make(map[string]string)
	for key, value := range pathParam {
		UserPathParam[key] = value
	}
	UserPathParam["user_id"] = userId
	at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap[url_name], nil, UserPathParam, nil)
}

func (at *AccountTest) InviteAndAccept(pathParam map[string]string, email, rbac_role string) string {
	var userId string
	at.Reporter.StepReport("InviteAndAccept", func() {
		err := at.InviteUserToJoin(pathParam, email, rbac_role)
		if err != nil {
			allure.FailNow(err)
		}
		util.SecondTimeWait(8, 15, "Wait For Invitation Email")
		at.UserAcceptInvitation(email, at.OrgPathParam)
		userId, err = at.CheckUserExistence(pathParam, email,
			map[string]string{
				"rbac_role": rbac_role,
				"status":    "ACTIVE",
			})
		if err != nil {
			allure.FailNow(err)
		}
	})
	return userId
}
