package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
	"unicode"

	"github.com/Authing/authing-go-sdk/lib/enum"
	"github.com/Authing/authing-go-sdk/lib/management"
	"github.com/Authing/authing-go-sdk/lib/model"
	"github.com/gin-gonic/gin"
	"github.com/google/go-github/v40/github"
	"github.com/pkg/errors"
	"github.com/spf13/cast"
	"golang.org/x/oauth2"
	"gorm.io/gorm"

	. "github.com/pingcap/lark2es/github"
	"github.com/pingcap/lark2es/pkg/config"
	"github.com/pingcap/lark2es/pkg/db"
	"github.com/pingcap/lark2es/pkg/job"
	"github.com/pingcap/lark2es/pkg/util"
)

var private_host = "https://api.authing.pingcap.net"

type HREmployee struct {
	Id                 string `gorm:"primaryKey"`
	Name               string
	Email              string
	GithubId           string
	AreaEmailGroup     string
	Country            string
	CreateDate         string
	DeleteDate         string
	Active             bool
	InChina            bool
	EmploymentForm     string
	FirstDepartmentId  string
	SecondDepartmentId string
	ThirdDepartmentId  string
	FourthDepartmentId string
	GH_Email           string
	GH_ID              int64
	GH_Type            string
	GH_Company         string
	GH_Name            string
	GH_Email_gen1      string
	GH_Email_gen2      string
	Comfirm            int8
	JobNumber          string
	Huilianyi          bool
}

type HREmployeeDepartment struct {
	EmployeeId   string `gorm:"primaryKey"`
	DepartmentId string `gorm:"primaryKey"`
}

type HRDepartment struct {
	Id       string `gorm:"primaryKey"`
	Name     string
	ParentId string
	Level    int
}

func main() {
	config.InitConfig("config", "yaml", "./etc")
	hrDB, err := db.InitDb(config.GetString("db"))
	if err != nil {
		log.Fatal(err)
	}
	hrDB.AutoMigrate(&HREmployee{}, &HRDepartment{}, &HREmployeeDepartment{})

	job.Start()
	job.AddJob("@every 12h", job.JobRunner(func() {
		LoadAndSaveEmployees(hrDB)
		FetchDepartments(hrDB)
	}))

	if config.GetString("runonece") == "true" {
		LoadAndSaveEmployees(hrDB)
	}

	gin.SetMode(config.GetString("ginmode"))
	ginR := gin.Default()
	oauthCh := make(chan GithubOAuth, 20)
	RegisterRoute(ginR, oauthCh)
	srv := &http.Server{
		Addr:    config.GetString("host") + ":" + config.GetString("port"),
		Handler: ginR,
	}

	go dealOAuthCh(oauthCh, hrDB)

	go func() {
		if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatalf("Listen failed : %s\n", err)
		}
	}()

	util.GracefulShutdown(srv, nil)
	log.Println("Server exiting")
}

func dealOAuthCh(ch chan GithubOAuth, hrDB *gorm.DB) {
	for oauth := range ch {
		auth := oauth
		go func() {
			employee := HREmployee{}
			err := hrDB.Find(&employee, "email = ?", auth.Email).Error
			if err != nil {
				log.Println("[error] employee not exist ", auth.Email)
				return
			}
			employee.GH_Email = auth.GithubEmails
			if employee.GithubId != auth.Login {
				employee.GithubId = auth.Login
			}
			employee.Comfirm = 1
			hrDB.Save(employee)
		}()
	}
}

func FetchDepartments(db *gorm.DB) {
	userPoolId := "6114dd1f753f9920f1ca06cf"
	appSecret := os.Getenv("appsecret")
	client := management.NewClient(userPoolId, appSecret, private_host)
	departments, err := client.ExportAll()
	if err != nil {
		log.Println("[error] FetchDepartments ", err)
		return
	}

	newDeparts := []HRDepartment{}
	level := 0
	for _, department := range departments {
		recurDepartment(level, department, &newDeparts, "")
	}

	err = db.Save(newDeparts).Error
	if err != nil {
		log.Println("[error] DB save", err)
	}
}

func recurDepartment(level int, depart model.OrgNode, newDeparts *[]HRDepartment, parentId string) {
	*newDeparts = append(*newDeparts, HRDepartment{
		Id:       depart.Id,
		Name:     depart.Name,
		Level:    level,
		ParentId: parentId,
	})

	for _, childDepart := range *depart.Children {
		recurDepartment(level+1, childDepart, newDeparts, depart.Id)
	}
}

func LoadAndSaveEmployees(db *gorm.DB) {
	existEmployees := []HREmployee{}
	_ = db.Find(&existEmployees)
	exists := make(map[string]*HREmployee)
	for i := range existEmployees {
		employee := &existEmployees[i]
		employee.Active = false
		exists[employee.Id] = employee
	}

	userPoolId := "6114dd1f753f9920f1ca06cf"
	appSecret := os.Getenv("appsecret")
	client := management.NewClient(userPoolId, appSecret, private_host)

	needCustom := new(bool)
	*needCustom = true
	req := model.QueryListRequest{
		Limit:          100,
		SortBy:         enum.SortByCreatedAtAsc,
		WithCustomData: needCustom,
	}

	page := 1
	employees := make([]HREmployee, 0, 500)
	for {
		req.Page = page
		resp, err := client.GetUserList(req)
		if err != nil {
			return
		}

		for _, user := range resp.List {
			var employee *HREmployee
			var ok bool
			if employee, ok = exists[user.Id]; !ok {
				userName := ""
				if user.Name != nil {
					userName = *user.Name
				}

				country := "中国"
				if user.Country != nil {
					country = *user.Country
				}
				employee = &HREmployee{Id: user.Id, Name: userName,
					Active:  !*user.IsDeleted,
					Country: country,
				}

				if user.Email != nil {
					employee.Email = *user.Email
				}
			} else {
				employee.Active = true
				if user.Email != nil && employee.Email != *user.Email {
					employee.Email = *user.Email
				}
			}

			employee.Huilianyi = true
			skip := false
			if user.CustomData != nil {
				for _, customData := range user.CustomData {
					if customData.Key == "extGitHubID_112020_1253377289" {
						if employee.GithubId == "" {
							employee.GithubId = *customData.Value
						}
					} else if customData.Key == "jobNumber" {
						employee.JobNumber = *customData.Value
					} else if customData.Key == "extxyxt_112020_924945631" {
						skip = true
					} else if customData.Key == "join_time" {
						employee.CreateDate = (*customData.Value)[:10]
					} else if customData.Key == "extsfwzgqyg_112020_549457112" {
						if *customData.Value == "true" {
							employee.InChina = true
						} else {
							employee.InChina = false
						}
					} else if customData.Key == "officeName" {
						employee.AreaEmailGroup = *customData.Value
					} else if customData.Key == "employeeType" {
						employee.EmploymentForm = *customData.Value
					} else if customData.Key == "huilianyi_syn" {
						if *customData.Value == "FALSE" {
							employee.Huilianyi = false
						}
					}
				}
			}

			// todo cjl huilianyi skip what?
			if skip {
				// skip flag for uploading data to huilianyi
				// continue
			}

			if !ok {
				employees = append(employees, *employee)
			}
		}

		if len(resp.List) != 100 {
			break
		}
		page++
	}

	// mark delete employee
	for i := range existEmployees {
		employee := &existEmployees[i]
		if !employee.Active && employee.DeleteDate == "" {
			employee.DeleteDate = time.Now().Format("2006-01-02")
		}
	}
	employees = append(employees, existEmployees...)
	db.Save(employees)

	concurrentCt := 15
	concurrenct := make(chan struct{}, concurrentCt)
	for i := 0; i < concurrentCt; i++ {
		concurrenct <- struct{}{}
	}

	var wg sync.WaitGroup
	var lock sync.Mutex
	allEmployeeDepartment := []HREmployeeDepartment{}
	for i := range employees {
		<-concurrenct
		employee := &employees[i]
		wg.Add(1)
		go func() {
			defer func() {
				wg.Done()
				concurrenct <- struct{}{}
			}()

			err := CheckGithub(config.GetString("githubtoken"), employee)
			if err != nil {
				// cjl
				log.Printf("[error] github login %+v, %s\n", employee, err.Error())
			}
			eds := queryEmployeeIsDepart(client, employee, db)
			lock.Lock()
			allEmployeeDepartment = append(allEmployeeDepartment, eds...)
			lock.Unlock()
			time.Sleep(time.Second)
		}()
	}
	wg.Wait()

	db.Save(employees)
	db.Where("1=1").Delete(&HREmployeeDepartment{})
	db.Save(allEmployeeDepartment)
}

func queryEmployeeIsDepart(client *management.Client, employee *HREmployee, db *gorm.DB) []HREmployeeDepartment {
	resp, err := client.GetUserDepartments(model.GetUserDepartmentsRequest{Id: employee.Id})
	if err != nil {
		log.Println("[error] GetUserDepartments ", err)
		return nil
	}

	if resp == nil {
		return nil
	}

	employDeparts := []HREmployeeDepartment{}
	for _, item := range resp.List {
		depart := item.Department

		for level, levelId := range depart.Path {
			employDeparts = append(employDeparts, HREmployeeDepartment{EmployeeId: employee.Id, DepartmentId: levelId})
			if !item.IsMainDepartment {
				continue
			}

			if level == 1 {
				employee.FirstDepartmentId = levelId
			} else if level == 2 {
				employee.SecondDepartmentId = levelId
			} else if level == 3 {
				employee.ThirdDepartmentId = levelId
			} else if level == 4 {
				employee.FourthDepartmentId = levelId
			}
		}
	}

	return employDeparts
}

var (
	emailRegex   = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
	chineseRegex = regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]")
)

func isValidGitHubLogin(login string) bool {
	if login == "" || login == "N/A" || strings.ContainsAny(login, "無无") {
		return false
	}

	// Check if it contains Chinese characters
	for _, r := range login {
		if unicode.Is(unicode.Scripts["Han"], r) || (chineseRegex.MatchString(string(r))) {
			return false
		}
	}

	// Check if it's not an email address
	return !emailRegex.MatchString(login)
}

func CheckGithub(token string, employee *HREmployee) error {
	login := employee.GithubId
	if !isValidGitHubLogin(login) {
		employee.GithubId = ""
		log.Printf("Not Valid GithubId [%s], %+v\n", login, employee)
		return nil
	}

	ctx := context.Background()
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: token},
	)
	tc := oauth2.NewClient(ctx, ts)
	client := github.NewClient(tc)
	// Get user info via GitHub API
	gh_user, resp, err := client.Users.Get(ctx, login)
	if err != nil {
		switch err.(type) {
		case *github.RateLimitError:
			log.Println("GitHub hit rate limit", err)
		case *github.Error:
			if err.(*github.Error).Code == "missing" {
				log.Printf("missing user for [%s], skip\n", login)
			}
		}

		if resp != nil && resp.StatusCode == 404 {
			log.Printf("missing user for [%s], skip\n", login)
		}
		employee.GithubId = ""
		return err
	}

	if gh_user.Login == nil {
		log.Printf("Unexpected gh_user.Login is nil for [%s]\n", login)
		return nil
	}

	employee.GithubId = *gh_user.Login
	employee.GH_Type = cast.ToString(gh_user.Type)
	employee.GH_Company = cast.ToString(gh_user.Company)
	employee.GH_Name = cast.ToString(gh_user.Name)
	employee.GH_ID = cast.ToInt64(gh_user.ID)
	employee.GH_Email_gen1 = employee.GithubId + "@users.noreply.github.com"
	employee.GH_Email_gen2 = strconv.FormatInt(employee.GH_ID, 10) + "+" + employee.GithubId + "@users.noreply.github.com"

	return nil
}
