package iam

import (
	"fmt"
	"net/http"
	"reflect"

	"github.com/gavv/httpexpect/v2"
	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/qa-PlatformAPIAuto/common"
	"github.com/tidbcloud/qa-PlatformAPIAuto/models"
	"github.com/tidbcloud/qa-gourd/ProductModel"
	"github.com/tidbcloud/qa-gourd/ProductService"
	"github.com/tidbcloud/qa-gourd/util"
)

type AccountTest struct {
	w                *ProductService.WebRequestInfo
	URLInfoMap       map[string]ProductService.UrlInfo
	Reporter         *util.AllureReporter
	OrgPathParam     map[string]string
	ProjectPathParam map[string]string
	ClusterPathParam map[string]string
	DBaaSUserInfo    DBaaSUserInfo
	SQLUser          SQLUserInfo
	OrgSettings      DBaaSOrgSettings
}
type DBaaSOrgSettings struct {
	TimeZone        int32
	OrgName         string
	UserSetTimeZone string
	OrgType         string
	OrgTag          string
}
type DBaaSUserInfo struct {
	UserEmail     string
	UserRole      map[string]string
	UserId        string
	UserFirstName string
	UserLastName  string
	UserPhone     string
}

type SQLUserInfo struct {
	Auth_method  string
	Builtin_role string
	Custom_role  []string
	Password     string
	User_name    string
}

type SQLUserResp struct {
	Items []SQLUserInfo
	Total int
}

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

	return at
}

func (at *AccountTest) Init(w *ProductService.WebRequestInfo, UrlInfoMap map[string]ProductService.UrlInfo, reporter *util.AllureReporter) {
	at.w = w
	at.URLInfoMap = UrlInfoMap
	at.Reporter = reporter

	at.ProjectPathParam = map[string]string{
		"org_id":     at.w.User.OrgId,
		"project_id": at.w.User.ProjectId,
	}
	at.OrgPathParam = map[string]string{
		"org_id": at.w.User.OrgId,
	}

	if at.w.Cluster.ClusterId != "" {
		at.ClusterPathParam = map[string]string{
			"org_id":     at.w.User.OrgId,
			"project_id": at.w.User.ProjectId,
			"cluster_id": at.w.Cluster.ClusterId,
		}
	}
}
func (at *AccountTest) ResetPathParam(orgId, projectId string, clusterId ...string) {
	at.ProjectPathParam = map[string]string{
		"org_id":     orgId,
		"project_id": projectId,
	}
	at.OrgPathParam = map[string]string{
		"org_id": orgId,
	}
	if len(clusterId) != 0 {
		at.ClusterPathParam = map[string]string{
			"org_id":     orgId,
			"project_id": projectId,
			"cluster_id": clusterId[0],
		}
	}
}
func (at *AccountTest) SetDBaaSUserInfo(email string) {
	at.DBaaSUserInfo.UserEmail = email
	at.DBaaSUserInfo.UserRole = map[string]string{
		"org_role":     UserInitialOrgRole,
		"project_role": UserInitialProjectRole,
	}
}

func (at *AccountTest) SetDBaaSUserId() {
	at.DBaaSUserInfo.UserId, _ = at.CheckUserExistence(at.OrgPathParam, at.DBaaSUserInfo.UserEmail,
		map[string]string{
			"status": "ACTIVE",
		},
	)
}

func (at *AccountTest) SetDBaaSUserProfileV2() {
	UserProfile, err := at.GetUserProfileV2()
	if err != nil {
		assert.Fail(at.Reporter.T, fmt.Sprintf("GetUserProfileV2 failed, err is %s", err))
	}
	at.DBaaSUserInfo.UserFirstName = UserProfile.FirstName
	at.DBaaSUserInfo.UserLastName = UserProfile.LastName
	at.DBaaSUserInfo.UserPhone = UserProfile.PhoneNumber

	OrgInfo, err := common.GetOrgInfoFromUserProfileV2(UserProfile, at.w.User.OrgId)
	if err != nil {
		assert.Fail(at.Reporter.T, fmt.Sprintf("GetOrgInfoFromUserProfileV2 failed, err is %s", err))
	}
	at.OrgSettings.TimeZone = OrgInfo.Timezone
	at.OrgSettings.OrgName = OrgInfo.Name
	at.OrgSettings.UserSetTimeZone = OrgInfo.UserSetTimezone
	at.OrgSettings.OrgType = OrgInfo.OrgType
	at.OrgSettings.OrgTag = OrgInfo.OrgTag
}

func (at *AccountTest) getOnlyOrgIdProjectIdFromUserProfileV2() (string, string) {
	UserProfile, err := at.GetUserProfileV2()
	if err != nil {
		assert.Fail(at.Reporter.T, fmt.Sprintf("GetUserProfileV2 failed, err is %s", err))
	}

	OrgInfo, err := common.GetOrgsFromUserProfileV2(UserProfile)
	if err != nil {
		assert.Fail(at.Reporter.T, fmt.Sprintf("GetOrgInfoFromUserProfileV2 failed, err is %s", err))
	}

	for _, org := range OrgInfo {
		projects, err := common.GetProjectsFromUserProfileV2(UserProfile, org.Id)
		if err != nil {
			return "", ""
		}
		for _, project := range *projects {
			return org.Id, project.Id
		}
	}
	return "", ""
}

func (at *AccountTest) SetSQLUserInfo(tokenPrefix string) {
	if tokenPrefix != "" {
		tokenPrefix = tokenPrefix + "."
	}
	at.SQLUser.Auth_method = AuthMethod
	if BuiltinRole != "role_admin" {
		at.SQLUser.Builtin_role = tokenPrefix + BuiltinRole
	} else {
		at.SQLUser.Builtin_role = BuiltinRole
	}
	at.SQLUser.Custom_role = []string{CustomRole}
	at.SQLUser.Password = Password
	at.SQLUser.User_name = tokenPrefix + UserName
}

// Operation : ['createSQLUser','editSQLUser','deleteSQLUser','listSQLUser']
func (at *AccountTest) OperateSQLUser(OperationType string, Body interface{}) (*httpexpect.Response, error) {
	if OperationType == "" {
		return nil, fmt.Errorf(fmt.Sprintf("Wrong input params , OperationType : %s, Body : %v", OperationType, Body))
	}

	OperateSQLUserResp := at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap[OperationType], Body, at.ClusterPathParam, nil)

	if OperateSQLUserResp.Raw().StatusCode != http.StatusOK {
		return OperateSQLUserResp, fmt.Errorf(fmt.Sprintf("%s failed , status code is : %v , message is : %v ",
			OperationType,
			OperateSQLUserResp.Raw().StatusCode,
			OperateSQLUserResp.JSON().Path(MessagePath)))
	}

	return OperateSQLUserResp, nil
}
func (at *AccountTest) CheckSQLUserExistence() (bool, error) {
	SQLUserResp, err := at.ListSQLUser()
	if err != nil {
		return false, err
	}

	for _, item := range SQLUserResp.Items {
		if item.User_name == at.SQLUser.User_name &&
			item.Auth_method == at.SQLUser.Auth_method &&
			item.Builtin_role == at.SQLUser.Builtin_role &&
			reflect.DeepEqual(item.Custom_role, at.SQLUser.Custom_role) {
			return true, nil
		}
	}
	return false, fmt.Errorf(fmt.Sprintf("Can't find SQL User %s with builtin-role : %s , custom role : %v", at.SQLUser.User_name, at.SQLUser.Builtin_role, at.SQLUser.Custom_role))
}
func (at *AccountTest) GetUserProfileV2() (*ProductModel.UserProfileV2, error) {
	getUserProfileV2Resp := at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap["getUserProfileV2"], nil, nil, nil)
	UserProfile, err := util.UnmarshalResult(getUserProfileV2Resp, ProductModel.UserProfileV2{})

	return &UserProfile, err
}

// return org list from orgsv1
func (at *AccountTest) GetOrgsV1() (*common.OrgList, error) {
	getOrgsResp := at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap["orgs"], nil, nil, nil)
	orgList, err := util.UnmarshalResult(getOrgsResp, common.OrgList{})

	return &orgList, err
}

func (at *AccountTest) GetAllProjects() (*models.CentralProjects, error) {
	// get project name
	type queryParam struct {
		Page     int `url:"page"`
		Per_page int `url:"per_page"`
	}
	param := queryParam{1, 100}
	projectPathparam := map[string]string{"org_id": at.w.User.OrgId}
	getProjectResp := at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap["listProjects"], nil, projectPathparam, param)
	projectList, err := util.UnmarshalResult(getProjectResp, models.CentralProjects{})

	return &projectList, err
}

func (at *AccountTest) GetCurrentUserRole() string {
	pathParam := map[string]string{
		"org_id": at.w.User.OrgId,
	}
	resp := at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap["getUserInfo"], nil, pathParam, nil)
	resp.Status(http.StatusOK)
	return resp.JSON().Path("$.role").String().Raw()
}

// use {org_id : x, project_id : y} or {org_id : x} to determine user's org or project role.
func (at *AccountTest) CheckUserExistence(pathParam map[string]string, email string, checkItem map[string]string) (string, error) {
	if email == "" {
		return "", fmt.Errorf(fmt.Sprintf("Wrong input params , email : %s", email))
	}
	var url_name string
	if _, ok := pathParam["project_id"]; ok {
		url_name = "getProjectUserAccessList"
	} else {
		url_name = "getOrgUserAccessList"
	}

	getUserAccessListResp := at.w.DoWebAPIRequestByUrlInfo(at.URLInfoMap[url_name], nil, pathParam, map[string]string{"page": "1", "per_page": "1000"})
	if getUserAccessListResp.Raw().StatusCode != http.StatusOK {
		return "", fmt.Errorf("%s failed , path param is : %+v , resp is : %v", url_name, pathParam, getUserAccessListResp.Raw())
	}

	var userId string
	for item, expect_value := range checkItem {
		userId = common.CheckInviteAccess(getUserAccessListResp, email, item, expect_value)
		if userId == "" {
			return userId, fmt.Errorf(fmt.Sprintf("Can't find user [ %s ] in %s of %s = %s ", email, url_name, item, expect_value))
		}
	}

	return userId, nil
}
