package feishu

import (
	"bufio"
	"errors"
	"fmt"
	"github.com/axgle/mahonia"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// Define the constant information related to Feishu
const (
	FS_App_ID      = "cli_a56d5cad7a3f500e"             //机器人的 ID
	FS_App_Secrets = "Q1yMbsmLEp6B0VNPTS7VYH0rDXkZjnNM" //机器人的 secret

	approval_code    = "0CBDD647-7223-43B4-BA0C-2F28C5E9BC55" //飞书流程的 ID
	Regx_InstancesID = `[0-9A-F]{8}-([0-9A-Z-]{4}-){3}[0-9A-Z]{12}`
)

var Instances_IDS_Str string = ""
var Config_instance_Path string = "" //  /config/instancesid.txt

// 定义从飞书 API 中抓出来的数据
type FeishuData struct {
	email          string   //申请人的邮箱
	apptype        bool     //申请类型是否符合，ture 为符合，false表示其它申请类型，仅支持 生产权限变更 和 生产账号申请（SSO） 处理
	prodaccount    string   //生产账号
	ssopermissions []string //权限集 存储到数组中
	daynum         string   //权限保持的天数
	isovertime     bool     //权限是否已经过期，true 表示已经过期，false 表示未过期
	isapproved     bool     //是否全部批准通过，true 为全部通过，false 表示还在批准中
	cnemail        string   //迁移新飞书后，增加该字段用于发送飞书消息
}

func Csh_Init() {
	//Get configuration file path
	Config_instance_Path = Get_Config_Instance_Path()
	Instances_IDS_Str = Read_txt_File(Config_instance_Path)
	//fmt.Println(Instances_IDS_Str)
}

func Get_Config_Instance_Path() string {
	dirs, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		os.Exit(0)
	}
	dirs = dirs + `/config/instancesid.txt`
	return dirs
}

/*
Get the app access token
string -->
*/
func Get_Tenant_AccessToken() (string, error) {
	var postdata string
	postdata = `{"app_id": "` + FS_App_ID + `","app_secret": "` + FS_App_Secrets + `"}`
	//fmt.Println(postdata)
	respdata, err := HTTP_Req_POST("https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal", postdata)
	if err != nil {
		return "", err
	}
	resp1, err1 := RegxStr(respdata, `"app_access_token":"t-[0-9a-zA-Z]+"`)
	if err1 != nil {
		return "", err
	}
	resp2, err2 := RegxStr(resp1, `t-[0-9a-zA-Z]+`)
	if err2 != nil {
		return "", err
	}
	return resp2, err
}

/*
Get the new application request, request every 5 seconds
Requested time period: from the timestamp 30 minutes ago to the current timestamp
string --> Apply for an instance ID within 30 minutes
*/

func Get_New_Instanceids() (string, error) {
	end_time, start_time, timeerr := GetTimeStamps()
	if timeerr != nil {
		return "", timeerr
	}
	//get the token
	APP_Token, aterr := Get_Tenant_AccessToken()
	if aterr != nil {
		return "", aterr
	}

	urls := `https://open.feishu.cn/open-apis/approval/v4/instances?approval_code=` + approval_code + `&end_time=` + end_time + `&page_size=100&start_time=` + start_time
	InsBody, ierr := HTTP_Req_GET(urls, "Authorization: Bearer "+APP_Token)
	if ierr != nil {
		return "", ierr
	}
	InsID, errk := RegxStr(InsBody, Regx_InstancesID)
	if errk != nil {
		return "", nil
	}
	return InsID, nil

}

/*
Get the detail information of instance
*/
func Instanceid_To_Feishudata(insid string) (FeishuData, error) {
	var fsdata FeishuData
	urls := `https://open.feishu.cn/open-apis/approval/v4/instances/` + strings.TrimSpace(insid)
	//get the token
	APP_Token, aterr := Get_Tenant_AccessToken()
	if aterr != nil {
		return fsdata, aterr
	}
	InsBody, ierr := HTTP_Req_GET(urls, "Authorization: Bearer "+APP_Token)
	if ierr != nil {
		return fsdata, ierr
	}

	//==========get the email=========
	if strings.Contains(InsBody, "申请人邮箱") == true {
		emailstr, eerr := RegxStr(InsBody, `申请人邮箱\\",\\"type\\":\\"input\\",\\"ext\\":null,\\"value\\":\\"[\w\. -]+@pingcap.com`)
		if eerr != nil {
			return fsdata, eerr
		}
		email := strings.TrimSpace(strings.ReplaceAll(emailstr, `申请人邮箱\",\"type\":\"input\",\"ext\":null,\"value\":\"`, ""))
		fsdata.email = email
	} else {
		emailstr, eerr := RegxStr(InsBody, `applicant email（xxx@pingcap.com）\\",\\"type\\":\\"input\\",\\"ext\\":null,\\"value\\":\\"[\w\. -]+@pingcap.com`)
		if eerr != nil {
			return fsdata, eerr
		}
		email := strings.TrimSpace(strings.ReplaceAll(emailstr, `applicant email（xxx@pingcap.com）\",\"type\":\"input\",\"ext\":null,\"value\":\"`, ""))
		fsdata.email = email
	}

	//==========get the app type===========
	if strings.Contains(InsBody, "申请人选择申请类型") == true {
		if strings.Contains(InsBody, `"申请人选择申请类型\",\"type\":\"radioV2\",\"ext\":null,\"value\":\"生产账号申请（SSO）\"`) == true {
			fsdata.apptype = true
		} else {
			fsdata.apptype = false
		}
	} else {
		if strings.Contains(InsBody, `"Applicant selects application type\",\"type\":\"radioV2\",\"ext\":null,\"value\":\"Production account application（SSO）\"`) == true {
			fsdata.apptype = true
		} else {
			fsdata.apptype = false
		}
	}

	//========= get the prod account ======
	if strings.Contains(InsBody, "生产账号选择") == true {
		accountstrs, aerr := RegxStr(InsBody, `\\"生产账号选择\\",\\"type\\":\\"radioV2\\",\\"ext\\":null,\\"value\\":\\"[\w- \(\#\)]+\\",`)
		if aerr != nil {
			return fsdata, aerr
		}
		accountstr := strings.ReplaceAll(accountstrs, `\"生产账号选择\",\"type\":\"radioV2\",\"ext\":null,\"value\":\"`, "")
		accountstr = strings.ReplaceAll(accountstr, `\",`, "")
		accountnum, aerr := RegxStr(accountstr, `\([\#0-9]+\)`)
		accountnum = strings.ReplaceAll(accountnum, "(#", "")
		accountnum = strings.ReplaceAll(accountnum, ")", "")
		if aerr != nil {
			return fsdata, aerr
		}
		fsdata.prodaccount = accountnum
	} else {
		accountstrs, aerr := RegxStr(InsBody, `\\"Production account selection\\",\\"type\\":\\"radioV2\\",\\"ext\\":null,\\"value\\":\\"[\w- \(\#\)]+\\",`)
		if aerr != nil {
			return fsdata, aerr
		}
		accountstr := strings.ReplaceAll(accountstrs, `\"Production account selection\",\"type\":\"radioV2\",\"ext\":null,\"value\":\"`, "")
		accountstr = strings.ReplaceAll(accountstr, `\",`, "")
		accountnum, aerr := RegxStr(accountstr, `\([\#0-9]+\)`)
		accountnum = strings.ReplaceAll(accountnum, "(#", "")
		accountnum = strings.ReplaceAll(accountnum, ")", "")
		if aerr != nil {
			return fsdata, aerr
		}

		//如果不为空,就说明解析出来的是 AWS 账号,空就再次尝试 GCP 账号解析
		if accountnum != "" {
			fsdata.prodaccount = accountnum
		} else if accountnum == "" {
			gcpstr, err := Feishudata_GCP_Account(InsBody)
			if err != nil {
				return fsdata, err
			}
			fsdata.prodaccount = gcpstr
		}
	}

	//========== get the sso role ========
	if strings.Contains(InsBody, "选择生产 SSO Role") == true {
		ssorolestrs, serr := RegxStr(InsBody, `\\"选择生产 SSO Role\\",\\"type\\":\\"checkboxV2\\",\\"ext\\":null,\\"value\\":\[\\"[\w-\\\",]+\\"\],`)
		if serr != nil {
			return fsdata, serr
		}
		ssorolestr, serr := RegxStr(ssorolestrs, `\[\\"[\w-\\\",]+\\"\]`)
		if serr != nil {
			return fsdata, serr
		}
		ssoroles := strings.ReplaceAll(ssorolestr, "[", "")
		ssoroles = strings.ReplaceAll(ssoroles, "]", "")
		ssoroles = strings.ReplaceAll(ssoroles, `\"`, "")
		ssorole := strings.Split(ssoroles, ",")
		fsdata.ssopermissions = ssorole
	} else {
		ssorolestrs, serr := RegxStr(InsBody, `\\"Select the production SSO role\\",\\"type\\":\\"checkboxV2\\",\\"ext\\":null,\\"value\\":\[\\"[\w-\\\",]+\\"\],`)
		if serr != nil {
			return fsdata, serr
		}
		ssorolestr, serr := RegxStr(ssorolestrs, `\[\\"[\w-\\\",]+\\"\]`)
		if serr != nil {
			return fsdata, serr
		}
		ssoroles := strings.ReplaceAll(ssorolestr, "[", "")
		ssoroles = strings.ReplaceAll(ssoroles, "]", "")
		ssoroles = strings.ReplaceAll(ssoroles, `\"`, "")
		ssorole := strings.Split(ssoroles, ",")

		// 如果元素大于等于 1 则为 AWS 的 ROLE,否则尝试解析 GCP ROLE
		if ssoroles != "" {
			fsdata.ssopermissions = ssorole
		} else {
			gcp_roles, err := Feishudata_GCP_RoleName(InsBody)
			if err != nil {
				return fsdata, err
			}
			fsdata.ssopermissions = gcp_roles
		}
	}

	//========= get the day==========
	start_time_strs, terr := RegxStr(InsBody, `\\"start\\":\\"[\w-:\+]+\\",`) //2023.7.31 更新正则 \\"start\\":\\"[\w-:\+]+\\",    原来的正则为 \\"start\\":\\"[\w-:\+]+\\"\}
	if terr != nil {
		return fsdata, terr
	}
	start_time := strings.ReplaceAll(start_time_strs, `\"start\":\"`, "")
	start_time = strings.ReplaceAll(start_time, `\",`, "")

	end_time_strs, terr := RegxStr(InsBody, `\{\\"end\\":\\"[\w-:\+]+\\",`)
	if terr != nil {
		return fsdata, terr
	}
	end_time := strings.ReplaceAll(end_time_strs, `{\"end\":\"`, "")
	end_time = strings.ReplaceAll(end_time, `\",`, "")
	fsdata.isovertime = Over_Time(end_time)

	days, derr := Minus_Time(start_time, end_time)
	if days == "0" {
		days = "1"
	}
	if derr != nil {
		return fsdata, derr
	}
	fsdata.daynum = days

	//=====Determine if the application process is complete=========
	if strings.Contains(InsBody, `"status":"PENDING",`) == true {
		fsdata.isapproved = false
	} else {
		fsdata.isapproved = true
	}

	//============= 获取 pingcap.cn 邮箱=================
	if strings.Contains(InsBody, `\"pingcap.cn email（xxx@pingcap.cn）\",\"type\":\"input\",\"ext\":null,\"value\":\"`) == true {
		cn_email, cerrs := RegxStr(InsBody, `\\"pingcap.cn email（xxx@pingcap.cn）\\",\\"type\\":\\"input\\",\\"ext\\":null,\\"value\\":\\"[^@]+@pingcap.cn\\"}`)
		if cerrs != nil {
			return fsdata, cerrs
		}
		cn_email = strings.ReplaceAll(cn_email, `\"pingcap.cn email（xxx@pingcap.cn）\",\"type\":\"input\",\"ext\":null,\"value\":\"`, "")
		cn_email = strings.ReplaceAll(cn_email, `\"}`, "")
		cn_email = strings.TrimSpace(cn_email)

		fsdata.cnemail = cn_email
	}
	return fsdata, nil
}

// 传入飞书 body,返回 GCP Account
func Feishudata_GCP_Account(InsBody string) (string, error) {
	var gcp_account string
	if strings.Contains(InsBody, `\"GCP Account\"`) == true {
		gcpstr, err := RegxStr(InsBody, `,\\"name\\":\\"GCP Project ID\\",\\"type\\":\\"radioV2\\",\\"ext\\":null,\\"value\\":\\"[^\\]+\\",`)
		if err != nil {
			return "", err
		}
		gcpstr = strings.ReplaceAll(gcpstr, `,\"name\":\"GCP Project ID\",\"type\":\"radioV2\",\"ext\":null,\"value\":\"`, "")
		gcpstr = strings.ReplaceAll(gcpstr, `\",`, "")
		gcpstr = strings.TrimSpace(gcpstr)
		gcp_account = gcpstr
	}

	return gcp_account, nil
}

// 传入飞书 body,返回 gcp sso role name
func Feishudata_GCP_RoleName(InsBody string) ([]string, error) {
	var gcpsso_spi []string
	if strings.Contains(InsBody, `\"GCP Account\"`) == true {
		gcpsso, err := RegxStr(InsBody, `\\"name\\":\\"Select the GCP production SSO role\\",\\"type\\":\\"checkboxV2\\",\\"ext\\":null,\\"value\\":\[[^\]]+`)
		if err != nil {
			return nil, err
		}
		gcpsso = strings.ReplaceAll(gcpsso, `\"name\":\"Select the GCP production SSO role\",\"type\":\"checkboxV2\",\"ext\":null,\"value\":[`, "")
		gcpsso = strings.ReplaceAll(gcpsso, `\"`, "")
		gcpsso = strings.ReplaceAll(gcpsso, " ", "")
		gcpsso_spi = strings.Split(gcpsso, `,`)
	}
	return gcpsso_spi, nil
}

/*
计算给出的时间是否过期,过期返回 true，不过期返回 false，StrTime 为时间
*/
func Over_Time(StrTime string) bool {
	//strTime := "2023-09-01T15:00:00+08:00"
	t, err := time.Parse(time.RFC3339, StrTime)
	if err != nil {
		return false
	}

	if t.Before(time.Now()) {
		return true
	} else {
		return false
	}
}

/*
Hurl --> http url
hv   --> custom header
string --> Return the requested data
*/
func HTTP_Req_GET(Hurl string, hv string) (string, error) {
	var spi []string
	if strings.Contains(hv, ":") == true {
		spi = strings.Split(hv, ":")
	}
	req, err := http.NewRequest("GET", Hurl, nil)
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0")
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2")
	req.Header.Set("Upgrade-Insecure-Requests", "1")
	if len(spi) >= 2 {
		req.Header.Set(spi[0], spi[1])
	}
	/*
		// Create a proxy URL
		proxyUrl, _ := url.Parse("http://127.0.0.1:8080")
		// create Transport
		transport := &http.Transport{
			Proxy: http.ProxyURL(proxyUrl),
		}
	*/

	client := &http.Client{
		Timeout: time.Millisecond * 10000,
		//Transport: transport,
	}
	resp2, err := client.Do(req)
	if err != nil {
		return "", err
	}

	body, err := ioutil.ReadAll(resp2.Body)
	if err != nil {
		return "", err
	}
	Retexecstr := mahonia.NewDecoder("UTF-8").ConvertString(string(body))
	resp2.Body.Close()
	return Retexecstr, nil
}

/*
Hurl --> http url
postdata   --> The data that needs to be submitted by post
string --> Return the requested data
*/
func HTTP_Req_POST(Hurl string, postdata string) (string, error) {
	req, err := http.NewRequest("POST", Hurl, strings.NewReader(postdata))
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0")
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2")
	//req.Header.Set("Accept-Encoding","gzip, deflate")
	req.Header.Set("Upgrade-Insecure-Requests", "1")
	req.Header.Set("Sec-Fetch-Dest", "document")
	req.Header.Set("Sec-Fetch-Mode", "navigate")
	req.Header.Set("Content-Type", "application/json")
	//req.Header.Set("Content-Type","application/x-www-form-urlencoded")
	//req.Header.Set("Cookie","lianan")
	client := &http.Client{
		Timeout: time.Millisecond * 10000,
	}
	resp2, err := client.Do(req)
	if err != nil {
		return "", err
	}
	body, err := ioutil.ReadAll(resp2.Body)
	if err != nil {
		return "", err
	}
	resp2.Body.Close()
	Retexecstr := mahonia.NewDecoder("gbk").ConvertString(string(body))
	return Retexecstr, nil
}

func RegxStr(datastr string, regx string) (string, error) {
	//Regular test, the MatchString function can only judge whether it can be matched, and return true if it can match, otherwise return false
	_, errs := regexp.MatchString(regx, datastr)
	if errs != nil {
		return "", errs
	}

	//Traverse the matching content
	comstr := regexp.MustCompile(regx)
	matchstr := comstr.FindAllString(datastr, -1)
	var regxres string = ""
	for x := range matchstr { //通过range函数遍历，打印全部输出
		regxres = regxres + matchstr[x] + "\n"
	}
	regxres = strings.TrimRight(regxres, "\n")
	return regxres, nil
}

// returen now ,past,nil
func GetTimeStamps() (string, string, error) {
	now := time.Now().UnixNano() / int64(time.Millisecond)
	past := now - 2*60*60*1000

	if past < 0 {
		return "0", "0", errors.New("invalid timestamp")
	}
	//fmt.Println(now)
	//fmt.Println(past)
	nowstr := strconv.FormatInt(now, 10)
	paststr := strconv.FormatInt(past, 10)
	//this is test
	//paststr = "1685548800000" //2023-06-01 00:00:00
	//paststr = "1709654400000"
	return nowstr, paststr, nil
}

// Read config file ,get the instanceid
func Read_txt_File(filepath string) string {
	var Datalists string
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("read file error!")
		}
	}()
	file, err := os.Open(filepath)
	if err == nil {
		buff := bufio.NewReader(file)
		for {
			line, _, err := buff.ReadLine()
			if err == io.EOF {
				break
			}
			Datalists = Datalists + string(line) + "\n"
			//time.Sleep(10*time.Millisecond)
		}
	}
	file.Close()
	return Datalists
}

// write data to file function
func WriteData_To_File(FilePathName string, Datastr string) error {
	f, err := os.OpenFile(FilePathName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		return err
	}

	_, err = f.Write([]byte(Datastr))
	if err != nil {
		return err
	}
	f.Close()
	return nil
}

/*
Computes the time difference function of two times
*/

func Minus_Time(start_time string, end_time string) (string, error) {
	/*
		start_time = "2023-06-09T00:00:00+08:00"
		end_time = "2023-06-05T10:30:00+08:00"
	*/
	// Parse the time string as time.Time type
	t1, err := time.Parse(time.RFC3339, start_time)
	if err != nil {
		return "", err
	}

	t2, err := time.Parse(time.RFC3339, end_time)
	if err != nil {
		return "", err
	}

	duration := t1.Sub(t2)

	days := duration / (24 * time.Hour)
	daysStr := strconv.Itoa(int(days))

	// Extract the numeric part of a string
	re := regexp.MustCompile("\\d+")
	daysNumStr := re.FindString(daysStr)

	return daysNumStr, nil
}
