package collectlogs

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strings"
	"time"

	"be.ems/src/framework/constants"
	"be.ems/src/framework/i18n"
	"be.ems/src/framework/reqctx"
	"be.ems/src/framework/resp"
	"be.ems/src/framework/utils/parse"
	"be.ems/src/modules/system/model"
	"be.ems/src/modules/system/service"

	"github.com/gin-gonic/gin"
)

const (
	// 业务操作类型-其它
	BUSINESS_TYPE_OTHER = "0"

	// 业务操作类型-新增
	BUSINESS_TYPE_INSERT = "1"

	// 业务操作类型-修改
	BUSINESS_TYPE_UPDATE = "2"

	// 业务操作类型-删除
	BUSINESS_TYPE_DELETE = "3"

	// 业务操作类型-授权
	BUSINESS_TYPE_GRANT = "4"

	// 业务操作类型-导出
	BUSINESS_TYPE_EXPORT = "5"

	// 业务操作类型-导入
	BUSINESS_TYPE_IMPORT = "6"

	// 业务操作类型-强退
	BUSINESS_TYPE_FORCE = "7"

	// 业务操作类型-清空数据
	BUSINESS_TYPE_CLEAN = "8"
)

const (
	// 操作人类别-其它
	OPERATOR_TYPE_OTHER = "0"

	// 操作人类别-后台用户
	OPERATOR_TYPE_MANAGE = "1"

	// 操作人类别-手机端用户
	OPERATOR_TYPE_MOBILE = "2"
)

// Option 操作日志参数
type Options struct {
	Title              string `json:"title"`              // 标题
	BusinessType       string `json:"businessType"`       // 类型，默认常量 BUSINESS_TYPE_OTHER
	OperatorType       string `json:"operatorType"`       // 操作人类别，默认常量 OPERATOR_TYPE_OTHER
	IsSaveRequestData  bool   `json:"isSaveRequestData"`  // 是否保存请求的参数
	IsSaveResponseData bool   `json:"isSaveResponseData"` // 是否保存响应的参数
}

// OptionNew 操作日志参数默认值
//
// 标题 "title":"--"
//
// 类型 "businessType": BUSINESS_TYPE_OTHER
//
// 注意之后JSON反序列使用：c.ShouldBindBodyWith(&params, binding.JSON)
func OptionNew(title, businessType string) Options {
	return Options{
		Title:              title,
		BusinessType:       businessType,
		OperatorType:       OPERATOR_TYPE_OTHER,
		IsSaveRequestData:  true,
		IsSaveResponseData: true,
	}
}

// OperateLog 访问操作日志记录
//
// 请在用户身份授权认证校验后使用以便获取登录用户信息
func OperateLog(options Options) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("startTime", time.Now())
		language := reqctx.AcceptLanguage(c)

		// 函数名
		funcName := c.HandlerName()
		lastDotIndex := strings.LastIndex(funcName, "/")
		funcName = funcName[lastDotIndex+1:]

		// 解析ip地址
		ipaddr, location := reqctx.IPAddrLocation(c)

		// 获取登录用户信息
		loginUser, err := reqctx.LoginUser(c)
		if err != nil {
			c.JSON(401, resp.CodeMsg(resp.CODE_AUTH_INVALID, i18n.TKey(language, err.Error())))
			c.Abort() // 停止执行后续的处理函数
			return
		}

		// 操作日志记录
		operLog := model.SysLogOperate{
			Title:          options.Title,
			BusinessType:   options.BusinessType,
			OperaMethod:    funcName,
			OperaUrl:       c.Request.URL.Path,
			OperaUrlMethod: c.Request.Method,
			OperaIp:        ipaddr,
			OperaLocation:  location,
			OperaBy:        loginUser.User.UserName,
		}

		// 是否需要保存request，参数和值
		if options.IsSaveRequestData {
			params := reqctx.RequestParamsMap(c)
			// 敏感属性字段进行掩码
			processSensitiveFields(params)
			jsonStr, _ := json.Marshal(params)
			paramsStr := string(jsonStr)
			if len(paramsStr) > 2000 {
				paramsStr = paramsStr[:2000]
			}
			operLog.OperaParam = paramsStr
		}

		// 调用下一个处理程序
		c.Next()

		// 响应状态
		status := c.Writer.Status()
		if status == 200 {
			operLog.StatusFlag = constants.STATUS_YES
		} else {
			operLog.StatusFlag = constants.STATUS_NO
		}

		// 是否需要保存response，参数和值
		if options.IsSaveResponseData {
			contentDisposition := c.Writer.Header().Get("Content-Disposition")
			contentType := c.Writer.Header().Get("Content-Type")
			content := contentType + contentDisposition
			msg := fmt.Sprintf(`{"status":"%d","size":"%d","content-type":"%s"}`, status, c.Writer.Size(), content)
			operLog.OperaMsg = msg
		}

		// 日志记录时间
		duration := time.Since(c.GetTime("startTime"))
		operLog.CostTime = duration.Milliseconds()
		operLog.OperaTime = time.Now().UnixMilli()

		// 保存操作记录到数据库
		service.NewSysLogOperate.Insert(operLog)
	}
}

// 敏感属性字段进行掩码
var maskProperties []string = []string{
	"password",
	"privateKey",
	"privatePassword",
	"passPhrase",
	"oldPassword",
	"newPassword",
	"confirmPassword",
	constants.ACCESS_TOKEN,
	constants.ACCESS_TOKEN_QUERY,
}

// processSensitiveFields 处理敏感属性字段
func processSensitiveFields(obj interface{}) {
	val := reflect.ValueOf(obj)

	switch val.Kind() {
	case reflect.Map:
		for _, key := range val.MapKeys() {
			value := val.MapIndex(key)
			keyStr := key.Interface().(string)

			// 遍历是否敏感属性
			hasMaskKey := false
			for _, v := range maskProperties {
				if v == keyStr {
					hasMaskKey = true
					break
				}
			}

			if hasMaskKey {
				valueStr := value.Interface().(string)
				if len(valueStr) > 100 {
					valueStr = valueStr[0:100]
				}
				val.SetMapIndex(key, reflect.ValueOf(parse.SafeContent(valueStr)))
			} else {
				processSensitiveFields(value.Interface())
			}
		}
	case reflect.Slice, reflect.Array:
		for i := 0; i < val.Len(); i++ {
			processSensitiveFields(val.Index(i).Interface())
		}
	}
}
