package service

import (
	"context"
	"os"
	"regexp"
	"strings"
	sync2 "sync"

	"github.com/spf13/viper"
	admin "google.golang.org/api/admin/directory/v1"

	"github.com/pingcap-inc/it-automation/internal/sync-google-email/model"
	"github.com/pingcap-inc/it-automation/internal/sync-google-email/service/sync"
	"github.com/pingcap-inc/it-automation/internal/sync-google-email/store/mysql"
	"github.com/pingcap-inc/it-automation/pkg/log"
	"github.com/pingcap-inc/it-automation/pkg/provider/authing"
	"github.com/pingcap-inc/it-automation/pkg/provider/google"
)

const (
	Pre         = "g"
	DeptNamePre = "G"
	MailTag     = "@pingcap.com"
)

var totalGroup map[string]bool

func authCompare(ctx context.Context) {
	log.L(ctx).Info("start authing compare.")

	// initialize
	authingService := authing.NewAuthRestService()
	userEmail := "liuy@pingcap.com"
	serviceAccountFile := viper.GetString("google.service-account")
	scope := []string{
		admin.AdminDirectoryGroupMemberScope,
		admin.AdminDirectoryGroupScope,
	}
	serviceAccount, err := os.ReadFile(serviceAccountFile)
	if err != nil {
		log.Fatalf("Error loading golden file: %s", err)
	}
	googleService, err := google.NewAdminSrv(ctx, userEmail, serviceAccount, scope...)
	if err != nil {
		log.L(ctx).Errorf("Error when calling `google.NewGroupsSettingService`: %s", err.Error())

		return
	}

	// db users
	mysqlStore, _ := mysql.GetMySQLFactoryOr(nil)
	userInDB, err := mysqlStore.User().ListUsers(ctx)
	if err != nil {
		log.L(ctx).Errorf("Error when calling `mysqlStore.User().ListUsers()`: %s", err.Error())

		return
	}

	userInDBMap := make(map[string]*model.LarkUser)
	for _, user := range userInDB {
		userInDBMap[user.Email] = user
	}

	// check department and user in google group
	orgNodes, err := authingService.ExportAll(ctx)
	if err != nil {
		log.L(ctx).Errorf("Error when calling `authingService.ExportAll`: %s", err.Error())

		return
	}
	var employeeToGroups sync2.Map
	var wg sync2.WaitGroup

	leavesMap := make(map[string][]string)

	totalGroup = make(map[string]bool)
	for _, node := range orgNodes {
		var path []string
		compareDep(ctx, node, &employeeToGroups, "", googleService, &wg, path, leavesMap, userInDBMap)
	}
	wg.Wait()

	// write to bitable
	biChan := make(chan int)
	go sync.SyncToBiTable(ctx, leavesMap, biChan)

	// delete the dept user not in
	mapChan := make(chan int, 8)
	employeeToGroups.Range(func(key, value any) bool {
		mapChan <- 1
		wg.Add(1)
		go func(k, v any) {
			defer func() {
				<-mapChan
				wg.Done()
			}()
			authingGroups, ok1 := v.([]string)
			agMap := make(map[string]bool)
			for _, ag := range authingGroups {
				agMap[ag] = false
			}
			userMail, ok2 := k.(string)
			if !ok1 || !ok2 {
				log.L(ctx).Errorf("error when get authing groups from employee %s", k.(string))

				return
			}

			groups, err := googleService.Groups.List().UserKey(userMail).Do()
			if err != nil {
				log.L(ctx).Errorf("error when get google groups from employee %s: %v", userMail, err.Error())

				return
			}

			// delete user should not in
			for _, group := range groups.Groups {
				_, ok := agMap[group.Email]
				if !ok && strings.HasPrefix(group.Email, Pre+"-") &&
					strings.HasPrefix(group.Name, DeptNamePre+"-") &&
					strings.HasPrefix(group.Description, Pre+":") {
					if group.Name == "G-R&D Group" && (userMail == "alice.xu@pingcap.com") {
						continue
					}

					if _, ok := totalGroup[group.Email]; !ok {
						continue
					}

					err = googleService.Members.Delete(group.Email, userMail).Do()
					if err != nil {
						log.L(ctx).Errorf("error when delete employee %s from group %s: %v", userMail, group.Email, err.Error())

						return
					}
					log.L(ctx).Infof("delete employee %s from group %s", userMail, group.Email)

				} else {
					agMap[group.Email] = true
				}
			}

			// add user should in
			for group, isExist := range agMap {
				if !isExist {
					insertMember(ctx, googleService, group, userMail)
				}
			}

		}(key, value)

		return true
	})
	wg.Wait()

	<-biChan
	log.L(ctx).Info("authing compare success.")
}

func compareDep(ctx context.Context, node authing.OrgNode, employeeGroups *sync2.Map,
	pre string, service *admin.Service, globalWg *sync2.WaitGroup, path []string, leavesMap map[string][]string, userInDBMap map[string]*model.LarkUser) {

	log.L(ctx).Debugf("compare dep %s", node.Name)

	globalWg.Add(1)
	defer globalWg.Done()
	deptMail, err := CheckGroup(ctx, pre, node.Name, node.Id, service)
	if err != nil {
		log.L(ctx).Errorf("Error when checkGroup: %s", err.Error())

		return
	}

	// depth more than 3
	if len(path) <= 3 {
		totalGroup[deptMail] = true
	}

	if deptMail == "" || deptMail == " " {
		return
	}

	var members []authing.Member
	if node.Members != nil && len(node.Members) > 0 {
		members = node.Members
	}

	// add path
	newPath := make([]string, len(path))
	copy(newPath, path)
	newPath = append(newPath, node.Name+":"+deptMail)

	// deal user
	// var wg sync2.WaitGroup
	// userChan := make(chan int, 2)
	for _, user := range members {
		userInFunc := user
		// wg.Add(1)
		// userChan <- 1
		// go func(userInFunc authing.Member) {
		// defer func() {
		// 	<-userChan
		// 	wg.Done()
		// }()

		// filter vie
		if node.Name == "平凯（北京）" {
			employeeGroups.Delete(userInFunc.Email)

			continue
		}

		// contractor
		if !strings.Contains(userInFunc.Email, MailTag) {
			log.Infof("user to ignore %s", userInFunc.Email)

			continue
		}
		// log.Debugf("compare userInFunc %s", userInFunc.Name)
		if userInFunc.Email == "" {
			return
		}

		// check user in db
		if _, ok := userInDBMap[userInFunc.Email]; !ok {
			log.Infof("user %s not in db", userInFunc.Email)

			continue
		}

		userInDB, ok := userInDBMap[userInFunc.Email]
		if !ok || userInDB.GoogleStatus != 1 {
			log.L(ctx).Infof("user %s not in google", userInFunc.Email)

			continue
		}

		// add userInFunc to all group he/she needed to be in
		for i, dept := range newPath {
			if i > 3 {
				break
			}
			dMail := strings.Split(dept, ":")[1]

			// save to map
			if value, ok := employeeGroups.Load(userInFunc.Email); !ok {
				employeeGroups.Store(userInFunc.Email, []string{dMail})
			} else {
				deptList := value.([]string)
				employeeGroups.Store(userInFunc.Email, append(deptList, dMail))
			}

			// check user in group
			isMember, err := IsGroupMember(ctx, service, dMail, userInFunc.Email)
			if err != nil {

				continue
			}
			if !isMember {
				err = insertMember(ctx, service, dMail, userInFunc.Email)
				if err != nil {
					continue
				}
			}

		}

		// }(user)
	}
	// wg.Wait()
	// close(userChan)

	if node.Name == "平凯（北京）" {
		return
	}

	// add department leader to email group
	var leaders []string
	leaders = append(leaders, node.LeaderUserIds...)
	if len(leaders) > 0 {
		for _, id := range node.LeaderUserIds {
			if id == "" {
				continue
			}

			mysqlStore, _ := mysql.GetMySQLFactoryOr(nil)
			user, err := mysqlStore.User().GetUser(id)
			if err != nil {
				log.L(ctx).Errorf("Error when calling `mysqlStore.User().GetUser`: %s. user id= %s", err.Error(), id)

				continue
			}

			// save to map
			if value, ok := employeeGroups.Load(user.Email); !ok {
				employeeGroups.Store(user.Email, []string{deptMail})
			} else {
				deptList := value.([]string)
				employeeGroups.Store(user.Email, append(deptList, deptMail))
			}

			isMember, err := IsGroupMember(ctx, service, deptMail, user.Email)
			if err != nil {
				continue
			}
			if !isMember {
				err := insertMember(ctx, service, deptMail, user.Email)
				if err != nil {
					continue
				}
			}
		}
	}

	// deal child department
	var wg sync2.WaitGroup
	if node.Children != nil {
		childChan := make(chan int, 2)
		for _, childDept := range node.Children {
			wg.Add(1)
			childChan <- 1
			go func(childNode authing.OrgNode) {
				defer func() {
					<-childChan
					wg.Done()
				}()

				compareDep(ctx, childNode, employeeGroups, node.Name, service, globalWg, newPath, leavesMap, userInDBMap)
				childMail := getDeptMail(node.Name, childNode.Name)
				if childMail == "" || childMail == " " {
					return
				}

				err = insertMember(ctx, service, deptMail, childMail)
				if err != nil {
					log.L(ctx).Errorf("error when insert member %s to group %s : %v", childMail, deptMail, err.Error())

				}
			}(childDept)
		}
		wg.Wait()
		close(childChan)
	}

	if len(path) <= 3 && (len(node.Children) == 0 || node.Children == nil || len(path) == 3) {
		leavesMap[deptMail] = newPath
	}

	log.L(ctx).Debugf("compare dep %s success.", node.Name)
}

func CheckGroup(ctx context.Context, preDept, nowDept, authingId string, service *admin.Service) (string, error) {
	deptMail := getDeptMail(preDept, nowDept)
	res, err := service.Groups.Get(deptMail).Do()
	if err != nil || res == nil {
		if strings.Contains(err.Error(), "Not Found") {
			log.L(ctx).Infof("group %s may not exist:%v", deptMail, err.Error())
			deptMail = createGroup(ctx, service, deptMail, nowDept, authingId)

			return deptMail, nil
		} else {
			log.L(ctx).Errorf("error when get group %s: %v", deptMail, err.Error())

			return "", err
		}
	}

	// deal deleted
	if strings.Contains(res.Name, Delete) {
		newName := strings.ReplaceAll(res.Name, Delete, "")
		_, err := service.Groups.Update(deptMail, &admin.Group{
			Name: newName,
		}).Do()
		if err != nil {
			log.L(ctx).Errorf("error when update group %s: %v", deptMail, err.Error())

			return "", err
		}

		log.L(ctx).Infof("update group %s -> %s success", res.Name, newName)
	}

	// compare authing id
	if strings.ReplaceAll(res.Description, Pre+":", "") != authingId {
		_, err := service.Groups.Update(deptMail, &admin.Group{
			Description: Pre + ":" + authingId,
		}).Do()
		if err != nil {
			log.L(ctx).Errorf("error when update group %s: %v", deptMail, err.Error())

			return "", err
		}

		log.L(ctx).Infof("update group %s authing id success", res.Name)
	}

	// compare name
	if res.Name != DeptNamePre+"-"+nowDept {
		_, err := service.Groups.Update(deptMail, &admin.Group{
			Name: DeptNamePre + "-" + nowDept,
		}).Do()
		if err != nil {
			log.L(ctx).Errorf("error when update group %s: %v", deptMail, err.Error())

			return "", err
		}

		log.L(ctx).Infof("update group %s name success", res.Name)
	}

	return deptMail, nil
}

func getDeptMail(preDept, nowDept string) string {
	re := regexp.MustCompile("[^A-Za-z0-9]")
	preSplits := re.Split(preDept, -1)
	deptMail := Pre
	tag := false
	if !(preDept == "" || preDept == " ") {
		for _, s := range preSplits {
			if s == "" || s == " " {
				continue
			}
			if !tag {
				tag = !tag
				deptMail += " "
			}
			deptMail += s[0:1]
		}
	}

	nowSplits := re.Split(nowDept, -1)
	for _, s := range nowSplits {
		if s == "" || s == " " {
			continue
		}
		deptMail += " " + s
	}

	lowerDeptMail := strings.ToLower(deptMail)
	return strings.ReplaceAll(lowerDeptMail, " ", "-") + MailTag
}

func createGroup(ctx context.Context, service *admin.Service, deptMail, deptName, authingId string) string {
	_, err := service.Groups.Insert(&admin.Group{
		Email:       deptMail,
		Name:        DeptNamePre + "-" + deptName,
		Description: Pre + ":" + authingId,
	}).Do()
	if err != nil {
		log.L(ctx).Errorf("Error when calling `service.Groups.Insert`: %s", err.Error())

		return ""
	}

	log.L(ctx).Infof("create group %s success", deptMail)

	return deptMail
}

func insertMember(ctx context.Context, service *admin.Service, groupMail, memberMail string) error {
	_, err := service.Members.Insert(groupMail, &admin.Member{
		Email: memberMail,
	}).Do()
	if err != nil {
		if strings.Contains(err.Error(), "Member already exists") {
			log.L(ctx).Debugf("member %s already exists in group %s", memberMail, groupMail)

			return nil
		} else {
			log.L(ctx).Errorf("Error when calling `service.Members.Insert`: %s", err.Error())

			return err
		}

	}
	log.L(ctx).Infof("insert member %s to group %s success", memberMail, groupMail)

	return nil
}

func IsGroupMember(ctx context.Context, service *admin.Service, groupMail, userMail string) (bool, error) {
	hasMember, err := service.Members.HasMember(groupMail, userMail).Do()
	if err != nil {
		log.L(ctx).Errorf("Error when query hasMember %s in group %s.:%v", userMail, groupMail, err)

		return false, err
	}

	return hasMember.IsMember, nil
}
