package routers

import (
	"encoding/json"
	"fmt"
	"sort"
	"strconv"
	"strings"

	"code.gitea.io/gitea/models"
	"code.gitea.io/gitea/modules/context"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/timeutil"
	"github.com/olivere/elastic/v7"
)

type SearchRes struct {
	Total        int64
	Result       []map[string]interface{}
	PrivateTotal int64
}

var client *elastic.Client

func InitESClient() {
	ESSearchUrl := setting.ESSearchURL
	var err error
	client, err = elastic.NewClient(elastic.SetSniff(false), elastic.SetURL(ESSearchUrl))
	if err != nil {
		log.Info("es init error.")
		//panic(err)
	}
}

func EmptySearch(ctx *context.Context) {
	log.Info("search template.")
	ctx.Data["Keyword"] = ""
	ctx.HTML(200, "explore/search_new")
}

func Search(ctx *context.Context) {
	log.Info("search template.")
	keyword := strings.Trim(ctx.Query("q"), " ")
	ctx.Data["Keyword"] = keyword
	ctx.Data["SortType"] = "newest"
	ctx.HTML(200, "explore/search_new")
}

func SearchApi(ctx *context.Context) {
	TableName := ctx.Query("TableName")
	Key := ctx.Query("Key")
	Page := ctx.QueryInt("Page")
	PageSize := ctx.QueryInt("PageSize")
	OnlyReturnNum := ctx.QueryBool("OnlyReturnNum")
	OnlySearchLabel := ctx.QueryBool("OnlySearchLabel")

	if Page <= 0 {
		Page = 1
	}
	if PageSize <= 0 || PageSize > 200 {
		PageSize = setting.UI.IssuePagingNum
	}
	if Key != "" && !OnlyReturnNum {
		go models.SaveSearchKeywordToDb(Key)
	}
	if TableName == "repository" {
		if OnlySearchLabel {
			searchRepoByLabel(ctx, Key, Page, PageSize)
		} else {
			searchRepo(ctx, "repository-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, OnlyReturnNum)
		}
		return
	} else if TableName == "issue" {
		searchIssueOrPr(ctx, "issue-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, OnlyReturnNum, "f")
		return
	} else if TableName == "user" {
		searchUserOrOrg(ctx, "user-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, true, OnlyReturnNum)
		return
	} else if TableName == "org" {
		searchUserOrOrg(ctx, "user-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, false, OnlyReturnNum)
		return
	} else if TableName == "dataset" {
		searchDataSet(ctx, "dataset-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, OnlyReturnNum)
		return
	} else if TableName == "pr" {
		searchIssueOrPr(ctx, "issue-es-index"+setting.INDEXPOSTFIX, Key, Page, PageSize, OnlyReturnNum, "t")
		//searchPR(ctx, "issue-es-index", Key, Page, PageSize, OnlyReturnNum)
		return
	}
}

func searchRepoByLabel(ctx *context.Context, Key string, Page int, PageSize int) {
	/*
			   项目， ES名称： repository-es-index
			   搜索:
			   name character varying(255) ,  项目名称
			   description text,  项目描述
			   topics json,  标签
			   排序:
			   updated_unix
			   num_watches,
		       num_stars,
		       num_forks,
	*/
	SortBy := ctx.Query("SortBy")
	PrivateTotal := ctx.QueryInt("PrivateTotal")
	WebTotal := ctx.QueryInt("WebTotal")
	ascending := ctx.QueryBool("Ascending")
	language := ctx.Query("language")
	if language == "" {
		language = "zh-CN"
	}
	from := (Page - 1) * PageSize
	resultObj := &SearchRes{}
	log.Info("WebTotal=" + fmt.Sprint(WebTotal))
	log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal))
	resultObj.Result = make([]map[string]interface{}, 0)
	if from == 0 {
		WebTotal = 0
	}
	if ctx.User != nil && (from < PrivateTotal || from == 0) {
		orderBy := models.SearchOrderByRecentUpdated
		switch SortBy {
		case "updated_unix.keyword":
			orderBy = models.SearchOrderByRecentUpdated
		case "num_stars":
			orderBy = models.SearchOrderByStarsReverse
		case "num_forks":
			orderBy = models.SearchOrderByForksReverse
		case "num_watches":
			orderBy = models.SearchOrderByWatches
		}
		log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil))
		repos, count, err := models.SearchRepository(&models.SearchRepoOptions{
			ListOptions: models.ListOptions{
				Page:     Page,
				PageSize: PageSize,
			},
			Actor:              ctx.User,
			OrderBy:            orderBy,
			Private:            true,
			OnlyPrivate:        true,
			TopicOnly:          true,
			TopicName:          Key,
			IncludeDescription: setting.UI.SearchRepoDescription,
		})
		if err != nil {
			ctx.JSON(200, "")
			return
		}
		resultObj.PrivateTotal = count
		if repos.Len() > 0 {
			log.Info("Query private repo number is:" + fmt.Sprint(repos.Len()))
			makePrivateRepo(repos, resultObj, Key, language)
		} else {
			log.Info("not found private repo,keyword=" + Key)
		}
		if repos.Len() >= PageSize {
			if WebTotal > 0 {
				resultObj.Total = int64(WebTotal)
				ctx.JSON(200, resultObj)
				return
			}
		}
	} else {
		if ctx.User == nil {
			resultObj.PrivateTotal = 0
		} else {
			resultObj.PrivateTotal = int64(PrivateTotal)
		}
	}

	from = from - PrivateTotal
	if from < 0 {
		from = 0
	}
	Size := PageSize - len(resultObj.Result)

	log.Info("query searchRepoByLabel start")
	if Key != "" {
		boolQ := elastic.NewBoolQuery()
		topicsQuery := elastic.NewMatchQuery("topics", Key)
		boolQ.Should(topicsQuery)

		res, err := client.Search("repository-es-index").Query(boolQ).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From(from).Size(Size).Highlight(queryHighlight("topics")).Do(ctx.Req.Context())
		if err == nil {
			searchJson, _ := json.Marshal(res)
			log.Info("searchJson=" + string(searchJson))
			esresult := makeRepoResult(res, "", false, language)
			resultObj.Total = resultObj.PrivateTotal + esresult.Total
			resultObj.Result = append(resultObj.Result, esresult.Result...)
			ctx.JSON(200, resultObj)
		} else {
			log.Info("query es error," + err.Error())
			ctx.JSON(200, "")
		}
	} else {
		ctx.JSON(200, "")
	}
}

func getSort(SortBy string, ascending bool, secondSortBy string, secondAscending bool) []elastic.Sorter {
	sort := make([]elastic.Sorter, 0)
	if SortBy == "default" || SortBy == "" {
		sort = append(sort, elastic.NewScoreSort())
		if secondSortBy != "" {
			log.Info("SortBy=" + SortBy + " secondSortBy=" + secondSortBy)
			sort = append(sort, elastic.NewFieldSort(secondSortBy).Order(secondAscending))
		}
	} else {
		sort = append(sort, elastic.NewFieldSort(SortBy).Order(ascending))
	}
	log.Info("sort size=" + fmt.Sprint(len(sort)))
	return sort
}

func searchRepo(ctx *context.Context, TableName string, Key string, Page int, PageSize int, OnlyReturnNum bool) {
	/*
			   项目， ES名称： repository-es-index
			   搜索:
			   name character varying(255) ,  项目名称
			   description text,  项目描述
			   topics json,  标签
			   排序:
			   updated_unix
			   num_watches,
		       num_stars,
		       num_forks,
	*/

	SortBy := ctx.Query("SortBy")
	PrivateTotal := ctx.QueryInt("PrivateTotal")
	WebTotal := ctx.QueryInt("WebTotal")
	ascending := ctx.QueryBool("Ascending")
	from := (Page - 1) * PageSize
	resultObj := &SearchRes{}
	log.Info("WebTotal=" + fmt.Sprint(WebTotal))
	log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal))
	resultObj.Result = make([]map[string]interface{}, 0)
	if from == 0 {
		WebTotal = 0
	}
	language := ctx.Query("language")
	if language == "" {
		language = "zh-CN"
	}
	if ctx.User != nil && (from < PrivateTotal || from == 0) {
		orderBy := models.SearchOrderByRecentUpdated
		switch SortBy {
		case "updated_unix.keyword":
			orderBy = models.SearchOrderByRecentUpdated
		case "num_stars":
			orderBy = models.SearchOrderByStarsReverse
		case "num_forks":
			orderBy = models.SearchOrderByForksReverse
		case "num_watches":
			orderBy = models.SearchOrderByWatches
		}
		log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil))
		repos, count, err := models.SearchRepository(&models.SearchRepoOptions{
			ListOptions: models.ListOptions{
				Page:     Page,
				PageSize: PageSize,
			},
			Actor:              ctx.User,
			OrderBy:            orderBy,
			Private:            true,
			OnlyPrivate:        true,
			Keyword:            Key,
			IncludeDescription: setting.UI.SearchRepoDescription,
			OnlySearchPrivate:  true,
		})
		if err != nil {
			ctx.JSON(200, "")
			return
		}
		resultObj.PrivateTotal = count
		if repos.Len() > 0 {
			log.Info("Query private repo number is:" + fmt.Sprint(repos.Len()))
			makePrivateRepo(repos, resultObj, Key, language)
		} else {
			log.Info("not found private repo,keyword=" + Key)
		}
		if repos.Len() >= PageSize {
			if WebTotal > 0 {
				resultObj.Total = int64(WebTotal)
				ctx.JSON(200, resultObj)
				return
			}
		}
	} else {
		if ctx.User == nil {
			resultObj.PrivateTotal = 0
		} else {
			resultObj.PrivateTotal = int64(PrivateTotal)
		}
	}

	from = from - PrivateTotal
	if from < 0 {
		from = 0
	}
	Size := PageSize - len(resultObj.Result)

	log.Info("query searchRepo start")
	if Key != "" {
		boolQ := elastic.NewBoolQuery()
		nameQuery := elastic.NewMatchQuery("alias", Key).Boost(1024).QueryName("f_first")
		descriptionQuery := elastic.NewMatchQuery("description", Key).Boost(1.5).QueryName("f_second")
		topicsQuery := elastic.NewMatchQuery("topics", Key).Boost(1).QueryName("f_third")
		boolQ.Should(nameQuery, descriptionQuery, topicsQuery)

		res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending, "num_stars", false)...).From(from).Size(Size).Highlight(queryHighlight("alias", "description", "topics")).Do(ctx.Req.Context())
		if err == nil {
			esresult := makeRepoResult(res, Key, OnlyReturnNum, language)
			setForkRepoOrder(esresult, SortBy)
			resultObj.Total = resultObj.PrivateTotal + esresult.Total
			isNeedSort := false
			if len(resultObj.Result) > 0 {
				isNeedSort = true
			}
			resultObj.Result = append(resultObj.Result, esresult.Result...)
			if isNeedSort {
				sortRepo(resultObj.Result, SortBy, ascending)
			}
			ctx.JSON(200, resultObj)
		} else {
			log.Info("query es error," + err.Error())
			ctx.JSON(200, "")
		}
	} else {
		log.Info("query all content.")
		//搜索的属性要指定{"timestamp":{"unmapped_type":"date"}}
		res, err := client.Search(TableName).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From(from).Size(Size).Do(ctx.Req.Context())
		if err == nil {
			searchJson, _ := json.Marshal(res)
			log.Info("searchJson=" + string(searchJson))
			esresult := makeRepoResult(res, "", OnlyReturnNum, language)
			resultObj.Total = resultObj.PrivateTotal + esresult.Total
			resultObj.Result = append(resultObj.Result, esresult.Result...)
			ctx.JSON(200, resultObj)
		} else {
			log.Info("query es error," + err.Error())
			ctx.JSON(200, "")
		}
	}
}

func setForkRepoOrder(esresult *SearchRes, SortBy string) {
	if SortBy == "default" || SortBy == "" {
		forkidMap := make(map[string]int, 0)
		for index, re := range esresult.Result {
			if re["fork_id"] != nil {
				fork_id := re["fork_id"].(string)
				if _, ok := forkidMap[fork_id]; !ok {
					forkidMap[fork_id] = index
				}
			}
		}
		for key, value := range forkidMap {
			for index, re := range esresult.Result {
				if re["id"].(string) == key {
					if value < index { //swap
						tmp := esresult.Result[index]
						esresult.Result[index] = esresult.Result[value]
						esresult.Result[value] = tmp
						break
					}
				}
			}
		}
	}
}

func sortRepo(Result []map[string]interface{}, SortBy string, ascending bool) {
	orderBy := ""
	switch SortBy {
	case "updated_unix.keyword":
		orderBy = "updated_unix"
	case "num_stars":
		orderBy = "num_stars"
	case "num_forks":
		orderBy = "num_forks"
	case "num_watches":
		orderBy = "num_watches"
	}
	sort.Slice(Result, func(i, j int) bool {
		return getInt(Result[i][orderBy], orderBy) > getInt(Result[j][orderBy], orderBy)
	})
}

func getInt(tmp interface{}, orderBy string) int64 {
	timeInt, err := strconv.ParseInt(fmt.Sprint(tmp), 10, 64)
	if err == nil {
		return timeInt
	} else {
		log.Info("convert " + orderBy + " error type=" + fmt.Sprint(tmp))
	}
	return -1
}

func makePrivateRepo(repos models.RepositoryList, res *SearchRes, keyword string, language string) {

	for _, repo := range repos {
		record := make(map[string]interface{})
		record["id"] = repo.ID
		record["name"] = makeHighLight(keyword, repo.Name)
		record["real_name"] = repo.Name
		record["owner_name"] = repo.OwnerName
		record["description"] = truncLongText(makeHighLight(keyword, repo.Description), true)

		hightTopics := make([]string, 0)
		if len(repo.Topics) > 0 {
			for _, t := range repo.Topics {
				hightTopics = append(hightTopics, makeHighLight(keyword, t))
			}
		}
		record["hightTopics"] = hightTopics

		record["num_watches"] = repo.NumWatches
		record["num_stars"] = repo.NumStars
		record["num_forks"] = repo.NumForks
		record["alias"] = truncLongText(makeHighLight(keyword, repo.Alias), true)
		record["lower_alias"] = repo.LowerAlias
		record["topics"] = repo.Topics
		record["avatar"] = repo.RelAvatarLink()
		if len(repo.RelAvatarLink()) == 0 {
			record["avatar"] = setting.RepositoryAvatarFallbackImage
		}
		record["updated_unix"] = repo.UpdatedUnix
		record["updated_html"] = timeutil.TimeSinceUnix(repo.UpdatedUnix, language)
		lang, err := repo.GetTopLanguageStats(1)
		if err == nil && len(lang) > 0 {
			record["lang"] = lang[0].Language
		} else {
			record["lang"] = ""
		}
		record["is_private"] = true
		res.Result = append(res.Result, record)
	}
}

func makeHighLight(keyword string, dest string) string {

	dest = replaceIngoreUpperOrLower(dest, strings.ToLower(dest), strings.ToLower(keyword))

	return dest
}

func replaceIngoreUpperOrLower(dest string, destLower string, keywordLower string) string {
	re := ""
	last := 0
	lenDestLower := len(destLower)
	lenkeywordLower := len(keywordLower)
	for i := 0; i < lenDestLower; i++ {
		if destLower[i] == keywordLower[0] {
			isFind := true
			for j := 1; j < lenkeywordLower; j++ {
				if (i+j) < lenDestLower && keywordLower[j] != destLower[i+j] {
					isFind = false
					break
				}
			}
			if isFind && (i+lenkeywordLower) <= lenDestLower {
				re += dest[last:i] + "\u003cfont color='red'\u003e" + dest[i:(i+lenkeywordLower)] + "\u003c/font\u003e"
				i = i + lenkeywordLower
				last = i
			}
		}
	}
	if last < lenDestLower {
		re += dest[last:lenDestLower]
	}
	return re
}

func makeRepoResult(sRes *elastic.SearchResult, Key string, OnlyReturnNum bool, language string) *SearchRes {
	total := sRes.Hits.TotalHits.Value
	result := make([]map[string]interface{}, 0)
	if !OnlyReturnNum {
		for i, hit := range sRes.Hits.Hits {
			log.Info("this is repo query " + fmt.Sprint(i) + " result.")
			recordSource := make(map[string]interface{})
			source, err := hit.Source.MarshalJSON()

			if err == nil {
				err = json.Unmarshal(source, &recordSource)
				if err == nil {
					record := make(map[string]interface{})
					record["id"] = hit.Id
					record["alias"] = getLabelValue("alias", recordSource, hit.Highlight)
					record["real_name"] = recordSource["name"]
					record["owner_name"] = recordSource["owner_name"]
					if recordSource["description"] != nil {
						desc := getLabelValue("description", recordSource, hit.Highlight)
						record["description"] = dealLongText(desc, Key, hit.MatchedQueries)
					} else {
						record["description"] = ""
					}

					record["hightTopics"] = jsonStrToArray(getLabelValue("topics", recordSource, hit.Highlight))
					record["num_watches"] = recordSource["num_watches"]
					record["num_stars"] = recordSource["num_stars"]
					record["num_forks"] = recordSource["num_forks"]
					record["lower_alias"] = recordSource["lower_alias"]
					record["fork_id"] = recordSource["fork_id"]
					if recordSource["topics"] != nil {
						topicsStr := recordSource["topics"].(string)
						log.Info("topicsStr=" + topicsStr)
						if topicsStr != "null" {
							record["topics"] = jsonStrToArray(topicsStr)
						}
					}
					if recordSource["avatar"] != nil {
						avatarstr := recordSource["avatar"].(string)
						if len(avatarstr) == 0 {
							// record["avatar"] = setting.RepositoryAvatarFallbackImage
						} else {
							record["avatar"] = setting.AppSubURL + "/repo-avatars/" + avatarstr
						}
					}
					record["updated_unix"] = recordSource["updated_unix"]
					setUpdateHtml(record, recordSource["updated_unix"].(string), language)

					record["lang"] = recordSource["lang"]
					record["is_private"] = false
					result = append(result, record)
				} else {
					log.Info("deal repo source error," + err.Error())
				}
			} else {
				log.Info("deal repo source error," + err.Error())
			}
		}
	}
	returnObj := &SearchRes{
		Total:  total,
		Result: result,
	}

	return returnObj
}

func setUpdateHtml(record map[string]interface{}, updated_unix string, language string) {
	timeInt, err := strconv.ParseInt(updated_unix, 10, 64)
	if err == nil {
		record["updated_html"] = timeutil.TimeSinceUnix(timeutil.TimeStamp(timeInt), language)
	}
}

func jsonStrToArray(str string) []string {
	b := []byte(str)
	strs := make([]string, 0)
	err := json.Unmarshal(b, &strs)
	if err != nil {
		log.Info("convert str arrar error, str=" + str)
	}
	return strs
}

func dealLongText(text string, Key string, MatchedQueries []string) string {
	var isNeedToDealText bool
	isNeedToDealText = false
	if len(MatchedQueries) > 0 && Key != "" {
		if MatchedQueries[0] == "f_second" || MatchedQueries[0] == "f_third" {
			isNeedToDealText = true
		}
	}
	return truncLongText(text, isNeedToDealText)
}

func truncLongText(text string, isNeedToDealText bool) string {
	startStr := "color="
	textRune := []rune(text)
	stringlen := len(textRune)
	if isNeedToDealText && stringlen > 200 {
		index := findFont(textRune, []rune(startStr))
		if index > 0 {
			start := index - 50
			if start < 0 {
				start = 0
			}
			end := index + 150
			if end >= stringlen {
				end = stringlen
			}
			return trimFontHtml(textRune[start:end]) + "..."
		} else {
			return trimFontHtml(textRune[0:200]) + "..."
		}
	} else {
		if stringlen > 200 {
			return trimFontHtml(textRune[0:200]) + "..."
		} else {
			return text
		}
	}
}

func trimFontHtml(text []rune) string {
	startRune := rune('<')
	endRune := rune('>')
	count := 0
	i := 0
	for ; i < len(text); i++ {
		if text[i] == startRune { //start <
			re := false
			j := i + 1
			for ; j < len(text); j++ {
				if text[j] == endRune {
					re = true
					break
				}
			}
			if re { //found >
				i = j
				count++
			} else {
				if count%2 == 1 {
					return string(text[0:i]) + "</font>"
				} else {
					return string(text[0:i])
				}
			}
		}
	}
	if count%2 == 1 {
		return string(text[0:i]) + "</font>"
	} else {
		return string(text[0:i])
	}
}

func trimHrefHtml(result string) string {
	result = strings.Replace(result, "</a>", "", -1)
	result = strings.Replace(result, "\n", "", -1)
	var index int
	for {
		index = findSubstr(result, 0, "<a")
		if index != -1 {
			sIndex := findSubstr(result, index+2, ">")
			if sIndex != -1 {
				result = result[0:index] + result[sIndex+1:]
			} else {
				result = result[0:index] + result[index+2:]
			}
		} else {
			break
		}
	}
	return result
}

func findFont(text []rune, childText []rune) int {
	for i := 0; i < len(text); i++ {
		if text[i] == childText[0] {
			re := true
			for j, k := range childText {
				if k != text[i+j] {
					re = false
					break
				}
			}
			if re {
				return i
			}
		}
	}
	return -1
}

func findSubstr(text string, startindex int, childText string) int {
	for i := startindex; i < len(text); i++ {
		if text[i] == childText[0] {
			re := true
			for k := range childText {
				if childText[k] != text[i+k] {
					re = false
					break
				}
			}
			if re {
				return i
			}
		}
	}
	return -1
}

func searchUserOrOrg(ctx *context.Context, TableName string, Key string, Page int, PageSize int, IsQueryUser bool, OnlyReturnNum bool) {
	/*
	   用户或者组织 ES名称： user-es-index
	   搜索:
	   name  ,  名称
	   full_name   全名
	   description  描述或者简介
	   排序:
	   created_unix
	   名称字母序
	*/
	SortBy := ctx.Query("SortBy")
	ascending := ctx.QueryBool("Ascending")
	boolQ := elastic.NewBoolQuery()

	typeValue := 1
	if IsQueryUser {
		typeValue = 0
	}
	UserOrOrgQuery := elastic.NewTermQuery("type", typeValue)
	if Key != "" {
		boolKeyQ := elastic.NewBoolQuery()
		log.Info("user or org Key=" + Key)
		nameQuery := elastic.NewMatchQuery("name", Key).Boost(2).QueryName("f_first")
		full_nameQuery := elastic.NewMatchQuery("full_name", Key).Boost(1.5).QueryName("f_second")
		descriptionQuery := elastic.NewMatchQuery("description", Key).Boost(1).QueryName("f_third")
		boolKeyQ.Should(nameQuery, full_nameQuery, descriptionQuery)
		boolQ.Must(UserOrOrgQuery, boolKeyQ)
	} else {
		boolQ.Must(UserOrOrgQuery)
	}

	res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From((Page - 1) * PageSize).Size(PageSize).Highlight(queryHighlight("name", "full_name", "description")).Do(ctx.Req.Context())
	if err == nil {
		searchJson, _ := json.Marshal(res)
		log.Info("searchJson=" + string(searchJson))
		result := makeUserOrOrgResult(res, Key, ctx, OnlyReturnNum)
		ctx.JSON(200, result)
	} else {
		log.Info("query es error," + err.Error())
		ctx.JSON(200, "")
	}
}

func getLabelValue(key string, recordSource map[string]interface{}, searchHighliht elastic.SearchHitHighlight) string {
	if value, ok := searchHighliht[key]; !ok {
		if recordSource[key] != nil {
			return recordSource[key].(string)
		} else {
			return ""
		}
	} else {
		return value[0]
	}
}

func makeUserOrOrgResult(sRes *elastic.SearchResult, Key string, ctx *context.Context, OnlyReturnNum bool) *SearchRes {
	total := sRes.Hits.TotalHits.Value
	result := make([]map[string]interface{}, 0)
	if !OnlyReturnNum {
		for i, hit := range sRes.Hits.Hits {
			log.Info("this is user query " + fmt.Sprint(i) + " result.")
			recordSource := make(map[string]interface{})
			source, err := hit.Source.MarshalJSON()

			if err == nil {
				err = json.Unmarshal(source, &recordSource)
				if err == nil {
					record := make(map[string]interface{})
					record["id"] = hit.Id
					record["name"] = getLabelValue("name", recordSource, hit.Highlight)
					record["real_name"] = recordSource["name"]
					record["full_name"] = getLabelValue("full_name", recordSource, hit.Highlight)
					if recordSource["description"] != nil {
						desc := getLabelValue("description", recordSource, hit.Highlight)
						record["description"] = dealLongText(desc, Key, hit.MatchedQueries)
					} else {
						record["description"] = ""
					}
					if ctx.User != nil {
						record["email"] = recordSource["email"]
					} else {
						record["email"] = ""
					}

					record["location"] = recordSource["location"]
					record["website"] = recordSource["website"]
					record["num_repos"] = recordSource["num_repos"]
					record["num_teams"] = recordSource["num_teams"]
					record["num_members"] = recordSource["num_members"]

					record["avatar"] = strings.TrimRight(setting.AppSubURL, "/") + "/user/avatar/" + recordSource["name"].(string) + "/" + strconv.Itoa(-1)
					record["updated_unix"] = recordSource["updated_unix"]
					record["created_unix"] = recordSource["created_unix"]
					record["add_time"] = getAddTime(recordSource["created_unix"].(string))
					result = append(result, record)
				} else {
					log.Info("deal user source error," + err.Error())
				}
			} else {
				log.Info("deal user source error," + err.Error())
			}
		}
	}
	returnObj := &SearchRes{
		Total:  total,
		Result: result,
	}
	return returnObj
}

func getAddTime(time string) string {
	timeInt, err := strconv.ParseInt(time, 10, 64)
	if err == nil {
		t := timeutil.TimeStamp(timeInt)
		return t.FormatShort()
	}
	return ""
}

func searchDataSet(ctx *context.Context, TableName string, Key string, Page int, PageSize int, OnlyReturnNum bool) {
	/*
	   数据集，ES名称：dataset-es-index
	   搜索:
	   title  ,  名称
	   description   描述
	   category  标签
	   file_name  数据集文件名称
	   排序:
	   download_times

	*/
	log.Info("query searchdataset start")
	SortBy := ctx.Query("SortBy")
	ascending := ctx.QueryBool("Ascending")
	PrivateTotal := ctx.QueryInt("PrivateTotal")
	WebTotal := ctx.QueryInt("WebTotal")
	language := ctx.Query("language")
	if language == "" {
		language = "zh-CN"
	}
	from := (Page - 1) * PageSize
	if from == 0 {
		WebTotal = 0
	}
	resultObj := &SearchRes{}
	log.Info("WebTotal=" + fmt.Sprint(WebTotal))
	log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal))
	resultObj.Result = make([]map[string]interface{}, 0)

	if ctx.User != nil && (from < PrivateTotal || from == 0) {

		log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil))
		datasets, count, err := models.SearchDatasetBySQL(Page, PageSize, Key, ctx.User.ID)
		if err != nil {
			ctx.JSON(200, "")
			return
		}
		resultObj.PrivateTotal = count
		datasetSize := len(datasets)
		if datasetSize > 0 {
			log.Info("Query private dataset number is:" + fmt.Sprint(datasetSize) + " count=" + fmt.Sprint(count))
			makePrivateDataSet(datasets, resultObj, Key, language)
		} else {
			log.Info("not found private dataset, keyword=" + Key)
		}
		if datasetSize >= PageSize {
			if WebTotal > 0 { //next page， not first query.
				resultObj.Total = int64(WebTotal)
				ctx.JSON(200, resultObj)
				return
			}
		}
	} else {
		resultObj.PrivateTotal = int64(PrivateTotal)
	}

	from = from - PrivateTotal
	if from < 0 {
		from = 0
	}
	Size := PageSize - len(resultObj.Result)

	boolQ := elastic.NewBoolQuery()
	if Key != "" {
		nameQuery := elastic.NewMatchQuery("title", Key).Boost(2).QueryName("f_first")
		descQuery := elastic.NewMatchQuery("description", Key).Boost(1.5).QueryName("f_second")
		fileNameQuery := elastic.NewMatchQuery("file_name", Key).Boost(1).QueryName("f_third")
		categoryQuery := elastic.NewMatchQuery("category", Key).Boost(1).QueryName("f_fourth")
		boolQ.Should(nameQuery, descQuery, categoryQuery, fileNameQuery)
		res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From(from).Size(Size).Highlight(queryHighlight("title", "description", "file_name", "category")).Do(ctx.Req.Context())
		if err == nil {
			searchJson, _ := json.Marshal(res)
			log.Info("searchJson=" + string(searchJson))
			esresult := makeDatasetResult(res, Key, OnlyReturnNum, language)
			resultObj.Total = resultObj.PrivateTotal + esresult.Total
			log.Info("query dataset es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total))
			resultObj.Result = append(resultObj.Result, esresult.Result...)
			ctx.JSON(200, resultObj)
		} else {
			log.Info("query es error," + err.Error())
		}
	} else {
		log.Info("query all datasets.")
		//搜索的属性要指定{"timestamp":{"unmapped_type":"date"}}
		res, err := client.Search(TableName).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From(from).Size(Size).Do(ctx.Req.Context())
		if err == nil {
			searchJson, _ := json.Marshal(res)
			log.Info("searchJson=" + string(searchJson))
			esresult := makeDatasetResult(res, "", OnlyReturnNum, language)
			resultObj.Total = resultObj.PrivateTotal + esresult.Total
			log.Info("query dataset es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total))
			resultObj.Result = append(resultObj.Result, esresult.Result...)
			ctx.JSON(200, resultObj)
		} else {
			log.Info("query es error," + err.Error())
			ctx.JSON(200, "")
		}
	}

}

func makePrivateDataSet(datasets []*models.Dataset, res *SearchRes, Key string, language string) {
	for _, dataset := range datasets {
		record := make(map[string]interface{})

		record["id"] = dataset.ID
		userId := dataset.UserID

		user, errUser := models.GetUserByID(userId)
		if errUser == nil {
			record["owerName"] = user.GetDisplayName()
			record["avatar"] = user.RelAvatarLink()
		}

		repo, errRepo := models.GetRepositoryByID(dataset.RepoID)
		if errRepo == nil {
			log.Info("repo_url=" + repo.FullName())
			record["repoUrl"] = repo.FullName()
			record["avatar"] = repo.RelAvatarLink()
		} else {
			log.Info("repo err=" + errRepo.Error())
		}

		record["title"] = makeHighLight(Key, dataset.Title)
		record["description"] = truncLongText(makeHighLight(Key, dataset.Description), true)

		record["category"] = dataset.Category
		record["task"] = dataset.Task
		record["download_times"] = dataset.DownloadTimes
		record["created_unix"] = dataset.CreatedUnix
		record["updated_unix"] = repo.UpdatedUnix
		record["updated_html"] = timeutil.TimeSinceUnix(repo.UpdatedUnix, language)

		res.Result = append(res.Result, record)
	}
}

func makeDatasetResult(sRes *elastic.SearchResult, Key string, OnlyReturnNum bool, language string) *SearchRes {
	total := sRes.Hits.TotalHits.Value
	result := make([]map[string]interface{}, 0)
	if !OnlyReturnNum {
		for i, hit := range sRes.Hits.Hits {
			log.Info("this is dataset query " + fmt.Sprint(i) + " result.")
			recordSource := make(map[string]interface{})
			source, err := hit.Source.MarshalJSON()

			if err == nil {
				err = json.Unmarshal(source, &recordSource)
				if err == nil {
					record := make(map[string]interface{})
					record["id"] = hit.Id
					userIdStr := recordSource["user_id"].(string)
					userId, cerr := strconv.ParseInt(userIdStr, 10, 64)
					if cerr == nil {
						user, errUser := models.GetUserByID(userId)
						if errUser == nil {
							record["owerName"] = user.GetDisplayName()
							record["avatar"] = user.RelAvatarLink()
						}
					}
					setRepoInfo(recordSource, record)
					record["title"] = getLabelValue("title", recordSource, hit.Highlight)
					record["category"] = getLabelValue("category", recordSource, hit.Highlight)
					if recordSource["description"] != nil {
						desc := getLabelValue("description", recordSource, hit.Highlight)
						record["description"] = dealLongText(desc, Key, hit.MatchedQueries)
					} else {
						record["description"] = ""
					}
					record["file_name"] = getDatasetFileName(getLabelValue("file_name", recordSource, hit.Highlight))
					record["task"] = recordSource["task"]
					record["download_times"] = recordSource["download_times"]
					record["created_unix"] = recordSource["created_unix"]
					setUpdateHtml(record, recordSource["updated_unix"].(string), language)
					result = append(result, record)
				} else {
					log.Info("deal dataset source error," + err.Error())
				}
			} else {
				log.Info("deal dataset source error," + err.Error())
			}
		}
	}
	returnObj := &SearchRes{
		Total:  total,
		Result: result,
	}

	return returnObj
}

func getDatasetFileName(fileName string) string {
	slices := strings.Split(fileName, "-#,#-")
	fileName = strings.Join(slices, ",  ")
	return fileName
}

func searchIssueOrPr(ctx *context.Context, TableName string, Key string, Page int, PageSize int, OnlyReturnNum bool, issueOrPr string) {

	/*
	   任务，合并请求  ES名称：issue-es-index
	   搜索:
	   name character varying(255) ,  标题
	   content text,  内容
	   comment text,  评论
	   排序:
	   updated_unix
	*/
	SortBy := ctx.Query("SortBy")
	ascending := ctx.QueryBool("Ascending")
	PrivateTotal := ctx.QueryInt("PrivateTotal")
	WebTotal := ctx.QueryInt("WebTotal")
	language := ctx.Query("language")
	if language == "" {
		language = "zh-CN"
	}
	from := (Page - 1) * PageSize
	if from == 0 {
		WebTotal = 0
	}
	resultObj := &SearchRes{}
	log.Info("WebTotal=" + fmt.Sprint(WebTotal))
	log.Info("PrivateTotal=" + fmt.Sprint(PrivateTotal))
	resultObj.Result = make([]map[string]interface{}, 0)
	isPull := false
	if issueOrPr == "t" {
		isPull = true
	}

	if ctx.User != nil && (from < PrivateTotal || from == 0) {

		log.Info("actor is null?:" + fmt.Sprint(ctx.User == nil))
		issues, count, err := models.SearchPrivateIssueOrPr(Page, PageSize, Key, isPull, ctx.User.ID)
		if err != nil {
			ctx.JSON(200, "")
			return
		}
		resultObj.PrivateTotal = count
		issuesSize := len(issues)
		if issuesSize > 0 {
			log.Info("Query private repo issue number is:" + fmt.Sprint(issuesSize) + " count=" + fmt.Sprint(count))
			makePrivateIssueOrPr(issues, resultObj, Key, language)
		} else {
			log.Info("not found private repo issue,keyword=" + Key)
		}
		if issuesSize >= PageSize {
			if WebTotal > 0 { //next page， not first query.
				resultObj.Total = int64(WebTotal)
				ctx.JSON(200, resultObj)
				return
			}
		}
	} else {
		resultObj.PrivateTotal = int64(PrivateTotal)
	}

	from = from - PrivateTotal
	if from < 0 {
		from = 0
	}
	Size := PageSize - len(resultObj.Result)

	boolQ := elastic.NewBoolQuery()
	isIssueQuery := elastic.NewTermQuery("is_pull", issueOrPr)

	if Key != "" {
		boolKeyQ := elastic.NewBoolQuery()
		log.Info("issue Key=" + Key)
		nameQuery := elastic.NewMatchQuery("name", Key).Boost(2).QueryName("f_first")
		contentQuery := elastic.NewMatchQuery("content", Key).Boost(1.5).QueryName("f_second")
		commentQuery := elastic.NewMatchQuery("comment", Key).Boost(1).QueryName("f_third")
		boolKeyQ.Should(nameQuery, contentQuery, commentQuery)
		boolQ.Must(isIssueQuery, boolKeyQ)
	} else {
		boolQ.Must(isIssueQuery)
	}

	res, err := client.Search(TableName).Query(boolQ).SortBy(getSort(SortBy, ascending, "updated_unix.keyword", false)...).From(from).Size(Size).Highlight(queryHighlight("name", "content", "comment")).Do(ctx.Req.Context())
	if err == nil {
		searchJson, _ := json.Marshal(res)
		log.Info("searchJson=" + string(searchJson))
		esresult := makeIssueResult(res, Key, OnlyReturnNum, language)

		resultObj.Total = resultObj.PrivateTotal + esresult.Total
		log.Info("query issue es count=" + fmt.Sprint(esresult.Total) + " total=" + fmt.Sprint(resultObj.Total))
		resultObj.Result = append(resultObj.Result, esresult.Result...)
		ctx.JSON(200, resultObj)
	} else {
		log.Info("query es error," + err.Error())
	}
}

func queryHighlight(names ...string) *elastic.Highlight {
	re := elastic.NewHighlight()
	for i := 0; i < len(names); i++ {
		field := &elastic.HighlighterField{
			Name: names[i],
		}
		re.Fields(field)
	}
	re.PreTags("<font color='red'>")
	re.PostTags("</font>")
	return re
}

func setRepoInfo(recordSource map[string]interface{}, record map[string]interface{}) {
	repoIdstr := recordSource["repo_id"].(string)
	repoId, cerr := strconv.ParseInt(repoIdstr, 10, 64)
	if cerr == nil {
		repo, errRepo := models.GetRepositoryByID(repoId)
		if errRepo == nil {
			log.Info("repo_url=" + repo.FullName())
			record["repoUrl"] = repo.FullName()
			record["avatar"] = repo.RelAvatarLink()
		} else {
			log.Info("repo err=" + errRepo.Error())
		}
	} else {
		log.Info("parse int err=" + cerr.Error())
	}
}

func makePrivateIssueOrPr(issues []*models.Issue, res *SearchRes, Key string, language string) {
	for _, issue := range issues {
		record := make(map[string]interface{})
		record["id"] = issue.ID
		record["repo_id"] = issue.RepoID

		repo, errRepo := models.GetRepositoryByID(issue.RepoID)
		if errRepo == nil {
			log.Info("repo_url=" + repo.FullName())
			record["repoUrl"] = repo.FullName()
			record["avatar"] = repo.RelAvatarLink()
		} else {
			log.Info("repo err=" + errRepo.Error())
		}
		record["name"] = makeHighLight(Key, issue.Title)
		record["content"] = truncLongText(makeHighLight(Key, issue.Content), true)

		if issue.IsPull {
			pr, err1 := issue.GetPullRequest()
			if err1 == nil && pr != nil {
				record["pr_id"] = pr.ID
			}
		}
		record["index"] = issue.Index
		record["num_comments"] = issue.NumComments
		record["is_closed"] = issue.IsClosed
		record["updated_unix"] = issue.UpdatedUnix
		record["updated_html"] = timeutil.TimeSinceUnix(issue.UpdatedUnix, language)
		res.Result = append(res.Result, record)
	}
}

func makeIssueResult(sRes *elastic.SearchResult, Key string, OnlyReturnNum bool, language string) *SearchRes {
	total := sRes.Hits.TotalHits.Value
	result := make([]map[string]interface{}, 0)
	if !OnlyReturnNum {
		for i, hit := range sRes.Hits.Hits {
			log.Info("this is issue query " + fmt.Sprint(i) + " result.")
			recordSource := make(map[string]interface{})
			source, err := hit.Source.MarshalJSON()

			if err == nil {
				err = json.Unmarshal(source, &recordSource)
				if err == nil {
					record := make(map[string]interface{})
					record["id"] = hit.Id
					record["repo_id"] = recordSource["repo_id"]
					log.Info("recordSource[\"repo_id\"]=" + fmt.Sprint(recordSource["repo_id"]))
					setRepoInfo(recordSource, record)
					record["name"] = getLabelValue("name", recordSource, hit.Highlight)
					if recordSource["content"] != nil {
						desc := getLabelValue("content", recordSource, hit.Highlight)
						record["content"] = dealLongText(desc, Key, hit.MatchedQueries)
						if _, ok := hit.Highlight["content"]; !ok {
							if _, ok_comment := hit.Highlight["comment"]; ok_comment {
								desc := getLabelValue("comment", recordSource, hit.Highlight)
								record["content"] = trimHrefHtml(dealLongText(desc, Key, hit.MatchedQueries))
							}
						}
					} else {
						if recordSource["comment"] != nil {
							desc := getLabelValue("comment", recordSource, hit.Highlight)
							record["content"] = dealLongText(desc, Key, hit.MatchedQueries)
						}
					}
					if recordSource["pr_id"] != nil {
						record["pr_id"] = recordSource["pr_id"]
					}
					log.Info("index=" + recordSource["index"].(string))
					record["index"] = recordSource["index"]
					record["num_comments"] = recordSource["num_comments"]
					record["is_closed"] = recordSource["is_closed"]
					record["updated_unix"] = recordSource["updated_unix"]
					setUpdateHtml(record, recordSource["updated_unix"].(string), language)
					result = append(result, record)
				} else {
					log.Info("deal issue source error," + err.Error())
				}
			} else {
				log.Info("deal issue source error," + err.Error())
			}
		}
	}
	returnObj := &SearchRes{
		Total:  total,
		Result: result,
	}

	return returnObj
}
