package controller

import (
	"glc/com"
	"glc/conf"
	"glc/gweb"
	"glc/ldb"
	"glc/ldb/search"
	"glc/ldb/sysmnt"
	"time"

	"github.com/gotoeasy/glang/cmn"
)

type storageItem struct {
	storeName     string // 日志仓
	total         uint32 // 日志件数
	isSearchRange bool   // 是否条件范围的日志仓
}

var cacheStoreNames []string // 所有的日志仓（避免每次读磁盘，适当使用缓存）
var cacheTime time.Time      // 最近一次读日志仓目录的时间点

// 日志检索（表单提交方式）
func LogSearchController(req *gweb.HttpRequest) *gweb.HttpResult {

	token := req.GetToken()
	username := ""
	if (!InWhiteList(req) && InBlackList(req)) || (conf.IsEnableLogin() && GetUsernameByToken(token) == "") {
		return gweb.Error403() // 黑名单检查、登录检查
	}
	if conf.IsEnableLogin() {
		username = GetUsernameByToken(token)
		catchSession.Set(token, username) // 会话重新计时
	}
	SetOrigin(req)

	// 准备好各种场景的检索条件（系统【~】、日志级别【!】、用户【@】）
	startTime := time.Now()
	mnt := sysmnt.NewSysmntStorage()
	cond := &search.SearchCondition{SearchSize: conf.GetPageSize()}
	cond.StoreName = req.GetFormParameter("storeName")                        // 日志仓条件
	cond.SearchKey = req.GetFormParameter("searchKey")                        // 输入的查询关键词
	cond.CurrentStoreName = req.GetFormParameter("currentStoreName")          // 滚动查询时定位用日志仓
	cond.CurrentId = cmn.StringToUint32(req.GetFormParameter("currentId"), 0) // 滚动查询时定位用ID
	cond.Forward = cmn.StringToBool(req.GetFormParameter("forward"), true)    // 是否向下滚动查询
	cond.OldNearId = cmn.StringToUint32(req.GetFormParameter("oldNearId"), 0) // 相邻检索旧ID
	cond.NewNearId = cmn.StringToUint32(req.GetFormParameter("newNearId"), 0) // 相邻检索新ID
	cond.NearStoreName = req.GetFormParameter("nearStoreName")                // 相邻检索时新ID对应的日志仓
	cond.DatetimeFrom = req.GetFormParameter("datetimeFrom")                  // 日期范围（From）
	cond.DatetimeTo = req.GetFormParameter("datetimeTo")                      // 日期范围（To）
	cond.OrgSystem = cmn.ToLower(cmn.Trim(req.GetFormParameter("system")))    // 系统
	cond.User = cmn.ToLower(cmn.Trim(req.GetFormParameter("user")))           // 用户
	cond.Loglevel = cmn.ToLower(req.GetFormParameter("loglevel"))             // 单选条件
	cond.Loglevels = cmn.Split(cond.Loglevel, ",")                            // 多选条件
	if cond.User != "" {
		cond.User = "@" + cond.User // 有指定用户条件
	}
	if len(cond.Loglevels) <= 1 || len(cond.Loglevels) >= 4 {
		cond.Loglevels = make([]string, 0) // 多选的单选或全选，都清空（单选走loglevel索引，全选等于没选）
	}
	if !cmn.IsBlank(cond.Loglevel) && !cmn.Contains(cond.Loglevel, ",") {
		cond.Loglevel = "!" + cmn.Trim(cond.Loglevel) // 编辑日志级别单选条件，以便精确匹配
	} else {
		cond.Loglevel = "" // 清空日志级别单选条件，以便多选配配（改用loglevels）
	}
	if !conf.IsEnableLogin() {
		cond.OrgSystems = append(cond.OrgSystems, "*") // 不需登录时全部系统都有访问权限
		if cond.OrgSystem != "" {
			cond.OrgSystem = "~" + cond.OrgSystem // 多个系统权限，按输入的系统作条件
		}
	} else {
		if username == conf.GetUsername() {
			// 管理员，不限系统
			cond.OrgSystems = append(cond.OrgSystems, "*")
			if cond.OrgSystem != "" {
				cond.OrgSystem = "~" + cond.OrgSystem // 多个系统权限，按输入的系统作条件
			}
		} else {
			// 一般用户，按设定权限
			user := mnt.GetSysUser(username)
			if user == nil {
				return gweb.Error403() // 可能出现，用户登录使用期间被管理员删除账号
			}
			if user.Systems == "*" {
				cond.OrgSystems = append(cond.OrgSystems, "*") // 全部系统都有访问权限
				if cond.OrgSystem != "" {
					cond.OrgSystem = "~" + cond.OrgSystem // 多个系统权限，按输入的系统作条件
				}
			} else {
				ary := cmn.Split(cmn.ToLower(user.Systems), ",")
				okSystem := false
				for i := 0; i < len(ary); i++ {
					cond.OrgSystems = append(cond.OrgSystems, "~"+ary[i]) // 仅设定的系统有访问权限
					if cond.OrgSystem == "" || cmn.EqualsIngoreCase(cond.OrgSystem, ary[i]) {
						okSystem = true
					}
				}

				if !okSystem {
					cond.OrgSystem = "-" // 输入的是没权限的系统时，写个不存在的条件用于快速返回
				} else {
					if len(ary) == 1 {
						cond.OrgSystem = "~" + ary[0] // 一共就一个系统权限，直接作为条件即可
					} else if cond.OrgSystem != "" {
						cond.OrgSystem = "~" + cond.OrgSystem // 多个系统权限，按输入的系统作条件
					}
				}
			}
		}
	}

	// 范围内的日志仓都查一遍
	// 注1）日志不断新增时，总件数可能会因为时间点原因不适最新，从而变现出点点小误差【完全可接受】
	// 注2）跨仓检索时，非本次检索的目标仓的话，只查取相关件数不做真正筛选计数以提高性能，最大匹配件数有时可能出现较大误差【折中可接受】
	result := &search.SearchResult{PageSize: cmn.IntToString(conf.GetPageSize())}
	var total uint32
	var count uint32
	storeItems := getStoreItems(cond.StoreName, cond.DatetimeFrom, cond.DatetimeTo)
	for i, max := 0, len(storeItems); i < max; i++ {
		item := storeItems[i]
		if !item.isSearchRange {
			// 不需要查数据，只查关联件数
			total += mnt.GetStorageDataCount(item.storeName) // 累加总件数
			continue
		}

		cond.SearchSize = conf.GetPageSize() - len(result.Data) // 本次需要查多少件
		if cond.CurrentStoreName != "" && item.storeName > cond.CurrentStoreName {
			cond.SearchSize = 0 // 是范围内的日志仓，但不是本次要查的，设为0不查数据，只查关联件数
		}

		eng := ldb.NewEngine(item.storeName)     // 遍历日志仓检索
		rs := eng.Search(cond)                   // 【检索】按动态的要求件数检索
		total += cmn.StringToUint32(rs.Total, 0) // 累加总件数
		count += cmn.StringToUint32(rs.Count, 0) // 累加最大匹配件数
		if len(rs.Data) > 0 {
			result.Data = append(result.Data, rs.Data...) // 累加查询结果
			result.LastStoreName = item.storeName         // 设定检索结果最后一条（最久远）日志所在的日志仓，页面向下滚动继续检索时定位用
		}

		if !(cond.CurrentStoreName != "" && item.storeName > cond.CurrentStoreName) {
			// 仅针对更久远的日志仓
			if len(result.Data) < conf.GetPageSize() && i < max-1 {
				// 数据没查够，且后面还有日志仓待查询，准备好跨仓查询条件
				cond.CurrentId = 0         // 下一日志仓从头开始查
				cond.CurrentStoreName = "" // 从头开始所以这个条件不再适用，清空
			}
		}
	}

	// 返回结果
	result.Total = cmn.Uint32ToString(total)                                      // 总件数
	result.Count = cmn.Uint32ToString(count)                                      // 最大匹配检索（笼统，在最大查取件数（5000件）内查完时，前端会改成精确的和结果一样的件数）
	result.TimeMessage = "耗时" + getTimeInfo(time.Since(startTime).Milliseconds()) // 查询耗时
	if cond.NewNearId > 0 && len(result.Data) == 0 {
		// 相邻检索时确保能返回定位日志
		result.Data = append(result.Data, search.GetLogDataModelById(cond.NearStoreName, cond.NewNearId))
	}
	return gweb.Result(result)
}

// 筛选出日志仓检索范围
func getStoreItems(storeName string, datetimeFrom string, datetimeTo string) []*storageItem {
	sysmntStore := sysmnt.NewSysmntStorage()
	var items []*storageItem
	if !conf.IsStoreNameAutoAddDate() {
		// 单日志仓
		name := com.GeyStoreNameByDate("")
		items = append(items, &storageItem{storeName: name, total: sysmntStore.GetStorageDataCount(name), isSearchRange: true})
		return items
	}

	// 遍历日志仓，比较日期范围筛选日志仓
	hasDateCond := (datetimeFrom != "" && datetimeTo != "")     // 是否有日期范围条件
	from := cmn.ReplaceAll(cmn.Left(datetimeFrom, 10), "-", "") // yyyymmdd或“”
	to := cmn.ReplaceAll(cmn.Left(datetimeTo, 10), "-", "")     // yyyymmdd或“”
	if time.Since(cacheTime) >= time.Second*10 {
		cacheStoreNames = com.GetStorageNames(conf.GetStorageRoot(), ".sysmnt") // 所有的日志仓，结果已排序，缓存10秒避免频繁读盘
		cacheTime = time.Now()
	}
	for i, max := 0, len(cacheStoreNames); i < max; i++ {
		name := cacheStoreNames[i]
		item := &storageItem{storeName: name, total: sysmntStore.GetStorageDataCount(name)}
		date := cmn.Right(name, 8) // yyyymmdd
		if storeName == "" {
			// 日志仓条件空白
			if hasDateCond {
				if date >= from && date <= to {
					item.isSearchRange = true // 日期范围内的日志仓都是条件范围
				}
			} else {
				item.isSearchRange = true // 无日志仓条件、且无日期条件，全部都是条件范围了
			}
		} else {
			// 有日志仓条件
			if hasDateCond {
				if storeName == name && date >= from && date <= to {
					item.isSearchRange = true // 有日期条件，得满足日期条件，该日志仓才是条件范围
				}
			} else {
				if storeName == name {
					item.isSearchRange = true // 没日期条件，仅该日志仓是条件范围
				}
			}
		}
		items = append(items, item)
	}
	return items
}

func getTimeInfo(milliseconds int64) string {
	if milliseconds >= 1000 {
		return " " + cmn.Float64ToString(cmn.Round1(float64(milliseconds)/1000.0)) + " 秒"
	}
	return " " + cmn.Int64ToString(milliseconds) + " 毫秒"
}
