package routers

import (
	"encoding/json"
	"fmt"
	"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"
	"github.com/olivere/elastic/v7"
)

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

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 {
		panic(err)
	}
}

func Search(ctx *context.Context) {
	TableName := ctx.Query("TableName")
	Key := ctx.Query("Key")
	Page := ctx.QueryInt("Page")
	PageSize := ctx.QueryInt("PageSize")
	if Page <= 0 {
		Page = 1
	}
	if PageSize <= 0 || PageSize > 200 {
		PageSize = setting.UI.IssuePagingNum
	}

	if TableName == "repository" {
		searchRepo(ctx, "repository-es-index", Key, Page, PageSize)
		return
	} else if TableName == "issue" {
		searchIssue(ctx, "issue-es-index", Key, Page, PageSize)
		return
	} else if TableName == "user" {
		searchUserOrOrg(ctx, "user-es-index", Key, Page, PageSize, true)
		return
	} else if TableName == "org" {
		searchUserOrOrg(ctx, "user-es-index", Key, Page, PageSize, false)
		return
	} else if TableName == "dataset" {
		searchDataSet(ctx, "dataset-es-index", Key, Page, PageSize)
		return
	} else if TableName == "pr" {
		searchPR(ctx, "issue-es-index", Key, Page, PageSize)
		return
	}
}

func searchRepoByLabel(ctx *context.Context, TableName string, 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")
	if SortBy == "" {
		SortBy = "updated_unix.keyword"
	}
	ascending := ctx.QueryBool("Ascending")
	log.Info("query searchRepoByLabel start")
	if Key != "" {
		boolQ := elastic.NewBoolQuery()
		topicsQuery := elastic.NewMatchQuery("topics", Key)
		boolQ.Should(topicsQuery)
		res, err := client.Search(TableName).Query(boolQ).Sort(SortBy, ascending).From((Page - 1) * PageSize).Size(PageSize).Do(ctx.Req.Context())
		if err == nil {
			result := makeRepoResult(res, "")
			ctx.JSON(200, result)
			return
		} else {
			log.Info("query es error," + err.Error())
		}
	}
	ctx.JSON(200, "")
}

func searchRepo(ctx *context.Context, TableName string, 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")
	if SortBy == "" {
		SortBy = "updated_unix.keyword"
	}
	ascending := ctx.QueryBool("Ascending")
	log.Info("query searchRepo start")
	if Key != "" {
		boolQ := elastic.NewBoolQuery()
		nameQuery := elastic.NewMatchQuery("name", 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(elastic.NewScoreSort(), elastic.NewFieldSort(SortBy).Order(ascending)).From((Page - 1) * PageSize).Size(PageSize).Do(ctx.Req.Context())
		if err == nil {
			result := makeRepoResult(res, Key)
			ctx.JSON(200, result)
		} 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(elastic.NewFieldSort(SortBy).Order(ascending)).From((Page - 1) * PageSize).Size(PageSize).Do(ctx.Req.Context())
		if err == nil {
			result := makeRepoResult(res, "")
			ctx.JSON(200, result)
		} else {
			log.Info("query es error," + err.Error())
			ctx.JSON(200, "")
		}
	}
}

func makeRepoResult(sRes *elastic.SearchResult, Key string) *SearchRes {
	total := sRes.Hits.TotalHits.Value
	result := make([]map[string]interface{}, 0)

	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["name"] = recordSource["name"]
				record["owner_name"] = recordSource["owner_name"]
				if recordSource["description"] != nil {
					desc := recordSource["description"].(string)
					record["description"] = dealLongText(desc, Key, hit.MatchedQueries)
				} else {
					record["description"] = ""
				}

				record["num_watches"] = recordSource["num_watches"]
				record["num_stars"] = recordSource["num_stars"]
				record["num_forks"] = recordSource["num_forks"]
				record["topics"] = recordSource["topics"]
				if recordSource["avatar"] != nil {
					record["avatar"] = setting.AppSubURL + "/repo-avatars/" + recordSource["avatar"].(string)
				}
				record["updated_unix"] = recordSource["updated_unix"]
				record["lang"] = recordSource["lang"]
				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 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
		}
	}
	stringlen := len(text)
	if isNeedToDealText && stringlen > 200 {
		index := strings.Index(text, Key)
		if index > 0 {
			start := index - 50
			if start < 0 {
				start = 0
			}
			end := index + 150
			if end >= stringlen {
				end = stringlen
			}
			return text[start:end]
		} else {
			return text[0:200]
		}
	} else {
		if stringlen > 200 {
			return text[0:200]
		} else {
			return text
		}
	}

}

func searchUserOrOrg(ctx *context.Context, TableName string, Key string, Page int, PageSize int, IsQueryUser bool) {
	/*
	   用户或者组织 ES名称： user-es-index
	   搜索:
	   name  ,  名称
	   full_name   全名
	   description  描述或者简介
	   排序:
	   created_unix
	   名称字母序
	*/
	SortBy := ctx.Query("SortBy")
	if SortBy == "" {
		SortBy = "updated_unix.keyword"
	}
	ascending := ctx.QueryBool("Ascending")
	boolQ := elastic.NewBoolQuery()
	if 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")
		boolQ.Should(nameQuery, full_nameQuery, descriptionQuery)
	}
	typeValue := 1
	if IsQueryUser {
		typeValue = 0
	}
	UserOrOrgQuery := elastic.NewTermQuery("type", typeValue)
	boolQ.Must(UserOrOrgQuery)

	res, err := client.Search(TableName).Query(boolQ).Sort(SortBy, ascending).From((Page - 1) * PageSize).Size(PageSize).Do(ctx.Req.Context())
	if err == nil {
		result := makeUserOrOrgResult(res, Key, ctx)
		ctx.JSON(200, result)
	} else {
		log.Info("query es error," + err.Error())
		ctx.JSON(200, "")
	}
}

func makeUserOrOrgResult(sRes *elastic.SearchResult, Key string, ctx *context.Context) *SearchRes {
	total := sRes.Hits.TotalHits.Value
	result := make([]map[string]interface{}, 0)

	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"] = recordSource["name"]
				record["full_name"] = recordSource["full_name"]
				if recordSource["description"] != nil {
					desc := recordSource["description"].(string)
					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"]
				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 searchDataSet(ctx *context.Context, TableName string, Key string, Page int, PageSize int) {
	/*
	   数据集，ES名称：dataset-es-index
	   搜索:
	   title  ,  名称
	   description   描述
	   category  标签
	   file_name  数据集文件名称
	   排序:
	   download_times

	*/
	SortBy := ctx.Query("SortBy")
	if SortBy == "" {
		SortBy = "download_times.keyword"
	}
	ascending := ctx.QueryBool("Ascending")
	log.Info("query searchRepo start")
	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).Sort(SortBy, ascending).From((Page - 1) * PageSize).Size(PageSize).Do(ctx.Req.Context())
		if err == nil {
			result := makeDatasetResult(res, Key)
			ctx.JSON(200, result)
		} else {
			log.Info("query es error," + err.Error())
		}
	} else {
		log.Info("query all content.")
		//搜索的属性要指定{"timestamp":{"unmapped_type":"date"}}
		res, err := client.Search(TableName).Sort(SortBy, ascending).From((Page - 1) * PageSize).Size(PageSize).Do(ctx.Req.Context())
		if err == nil {
			result := makeRepoResult(res, "")
			ctx.JSON(200, result)
		} else {
			log.Info("query es error," + err.Error())
			ctx.JSON(200, "")
		}
	}

}

func makeDatasetResult(sRes *elastic.SearchResult, Key string) *SearchRes {
	total := sRes.Hits.TotalHits.Value
	result := make([]map[string]interface{}, 0)

	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
				userId := recordSource["user_id"].(int64)
				user, errUser := models.GetUserByID(userId)
				if errUser == nil {
					record["owerName"] = user.GetDisplayName()
					record["avatar"] = user.RelAvatarLink()
				}
				record["title"] = recordSource["title"]
				record["category"] = recordSource["category"]
				if recordSource["description"] != nil {
					desc := recordSource["description"].(string)
					record["description"] = dealLongText(desc, Key, hit.MatchedQueries)
				} else {
					record["description"] = ""
				}
				record["download_times"] = recordSource["download_times"]
				record["created_unix"] = recordSource["created_unix"]
				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 searchIssue(ctx *context.Context, TableName string, Key string, Page int, PageSize int) {

	/*
	   任务，合并请求  ES名称：issue-es-index
	   搜索:
	   name character varying(255) ,  标题
	   content text,  内容
	   comment text,  评论
	   排序:
	   updated_unix
	*/
	SortBy := ctx.Query("SortBy")
	if SortBy == "" {
		SortBy = "updated_unix.keyword"
	}
	ascending := ctx.QueryBool("Ascending")
	boolQ := elastic.NewBoolQuery()
	isIssueQuery := elastic.NewTermQuery("is_pull", "f")
	boolQ.Must(isIssueQuery)

	if Key != "" {
		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")
		boolQ.Should(nameQuery, contentQuery, commentQuery)
	}

	res, err := client.Search(TableName).Query(boolQ).Sort(SortBy, ascending).From((Page - 1) * PageSize).Size(PageSize).Do(ctx.Req.Context())
	if err == nil {
		searchJson, _ := json.Marshal(res)
		log.Info("searchJson=" + string(searchJson))
		result := makeIssueResult(res, Key)
		ctx.JSON(200, result)
	} else {
		log.Info("query es error," + err.Error())
	}

}

func makeIssueResult(sRes *elastic.SearchResult, Key string) *SearchRes {
	total := sRes.Hits.TotalHits.Value
	result := make([]map[string]interface{}, 0)

	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"]))
				repoIdstr := recordSource["repo_id"].(string)
				repoId, cerr := strconv.ParseInt(repoIdstr, 10, 64)
				if cerr == nil {
					repo, errRepo := models.GetRepositoryByID(repoId)
					if errRepo == nil {
						record["repoUrl"] = repo.FullName()
						record["avatar"] = repo.RelAvatarLink()
					}
				}
				record["name"] = recordSource["name"]
				if recordSource["content"] != nil {
					desc := recordSource["content"].(string)
					record["content"] = dealLongText(desc, Key, hit.MatchedQueries)
				} else {
					record["content"] = ""
				}
				if recordSource["pr_id"] != nil {
					record["pr_id"] = recordSource["pr_id"]
				}
				if recordSource["comment"] != nil {
					desc := recordSource["comment"].(string)
					record["comment"] = dealLongText(desc, Key, hit.MatchedQueries)
				} else {
					record["comment"] = ""
				}
				record["num_comments"] = recordSource["num_comments"]
				record["updated_unix"] = recordSource["updated_unix"]
				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
}

func searchPR(ctx *context.Context, TableName string, Key string, Page int, PageSize int) {

	/*
	   任务，合并请求  ES名称：issue-es-index
	   搜索:
	   name character varying(255) ,  标题
	   content text,  内容
	   comment text,  评论
	   排序:
	   updated_unix
	*/
	SortBy := ctx.Query("SortBy")
	if SortBy == "" {
		SortBy = "updated_unix.keyword"
	}
	ascending := ctx.QueryBool("Ascending")
	boolQ := elastic.NewBoolQuery()
	if Key != "" {
		nameQuery := elastic.NewMatchQuery("name", Key).Boost(1024).QueryName("f_first")
		contentQuery := elastic.NewMatchQuery("content", Key).Boost(1.5).QueryName("f_second")
		commentQuery := elastic.NewMatchQuery("comment", Key).Boost(1).QueryName("f_third")
		boolQ.Should(nameQuery, contentQuery, commentQuery)
	}
	isIssueQuery := elastic.NewTermQuery("is_pull", "t")
	boolQ.Must(isIssueQuery)
	res, err := client.Search(TableName).Query(boolQ).Sort(SortBy, ascending).From((Page - 1) * PageSize).Size(PageSize).Do(ctx.Req.Context())
	if err == nil {
		result := makeIssueResult(res, Key)
		ctx.JSON(200, result)
	} else {
		log.Info("query es error," + err.Error())
	}

}
