package handle

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"strconv"
	"strings"

	"nms_nbi/lib/dborm"

	"github.com/gorilla/mux"
	g "github.com/gosnmp/gosnmp"

	"nms_nbi/lib/global"
	"nms_nbi/lib/log"
	"nms_nbi/lib/oauth"
	"nms_nbi/lib/services"
	"nms_nbi/lib/session"
	"nms_nbi/restagent/config"
)

var TodoList []stTodo

type stTodo struct {
	No    string
	Item  string
	Value string
}

type ErrorOAuthResponse struct {
	Error map[string]interface{}
}

type FailOAuthResponse struct {
	Error struct {
		ErrorCode string
		ErrorInfo string
	}
}

type ApiResponse struct {
	ResultCode    string
	ResultMessage interface{}
}

var globalSession = session.NewSessManager("restagent")

func init() {
	conf := config.GetYamlConfig()
	// Default is a pointer to a GoSNMP struct that contains sensible defaults
	// eg port 161, community public, etc
	g.Default.Target = conf.NE.Addr
	g.Default.Port = conf.NE.Port
	err := g.Default.Connect()
	if err != nil {
		log.Fatalf("Connect() err: %v", err)
	}
	//defer g.Default.Conn.Close()
}

/*
func IsValidOAuthInfo(oAuthBody OAuthBody) bool {
	log.Debug("IsValidOAuthInfo processing... ")

	conf := config.GetYamlConfig()
	for _, o := range conf.Auth {
		if oAuthBody.GrantType == o.Type && oAuthBody.UserName == o.User && oAuthBody.Value == o.Password {
			return true
		}
	}

	return false
}

func IsWrongOAuthInfo(oAuthBody OAuthBody) bool {
	log.Debug("IsWrongOAuthInfo processing... ")

	if oAuthBody.GrantType == "" || strings.ToLower(oAuthBody.GrantType) != "password" || oAuthBody.UserName == "" || oAuthBody.Value == ""  {
		return true
	}

	return false
}
*/

func IsValidOAuthUri(r *http.Request) bool {
	vars := mux.Vars(r)
	Uri := vars["apiCategory"] + "/" + vars["apiVersion"] // 获取Uri
	if Uri != "securityManagement/v1" {
		return false
	}

	return true
}

func IsVallidContentType(r *http.Request) bool {
	log.Debug("IsVallidContentType processing ...")

	ctype := r.Header["Content-Type"]
	if len(ctype) != 0 && !strings.Contains(ctype[0], "application/json") {
		return false
	}

	return true
}

func LoginFromOMC(w http.ResponseWriter, r *http.Request) {
	log.Debug("LoginFromOMC processing... ")

	body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小
	if err != nil {
		log.Debug(err)
		services.ResponseNotFound404UriNotExist(w, r)
		return
	}

	// check media type(content type) only support "application/json"
	/*	if !IsVallidContentType(r) {
			log.Debug("Invalid Content-Type")
			services.ResponseUnsupportedMediaType415(w)
			return
		}

		// check extend uri, response 404
		if !IsValidOAuthUri(r) {
			log.Debug("Uri is invalid")
			services.ResponseNotFound404UriNotExist(w, r)
			return
		}

	*/
	// Error process ....
	// response 400-7
	if !json.Valid([]byte(body)) {
		log.Debug("Invalid Json Format")
		services.ResponseBadRequest400InvalidJson(w)
		return
	}

	var oAuthBody oauth.OAuthBody
	_ = json.Unmarshal(body, &oAuthBody) //转为json
	log.Debug("body:", string(body), "oAuthBody:", oAuthBody)

	defer r.Body.Close()
	// response 400-5
	if oauth.IsWrongOAuthInfo(oAuthBody) {
		log.Debug("Wrong parameter value")
		services.ResponseBadRequest400WrongParamValue(w)
		return
	}
	/*
		if oauth.IsValidOAuthInfo(oAuthBody) {
			plist := config.GetPermissionFromConfig(oAuthBody.UserName, oAuthBody.GrantType)
			log.Debug("Permission list:", plist)

			token := globalSession.NewSession(w, r, plist)
			services.ResponseStatusOK200Login(w, token)
		} else {
			// response 400-4
			log.Debug("Authentication failed, mismatch user or password")

			services.ResponseBadRequest400IncorrectLogin(w)
		}
	*/
	validUser, changePassword, _ := dborm.XormCheckLoginUser(oAuthBody.UserName,
		oAuthBody.Value, config.GetYamlConfig().Auth.Crypt)
	if validUser {
		//		plist := config.GetPermissionFromConfig(oAuthBody.UserName, oAuthBody.GrantType)
		plist := []bool{true, true, true, true}
		log.Debug("Permission list:", plist)

		token := globalSession.NewSession(w, r, plist)
		services.ResponseStatusOK200Login(w, token, changePassword)
	} else {
		// response 400-4
		log.Error("Authentication failed, mismatch user or password")

		services.ResponseBadRequest400IncorrectLogin(w)
	}
	return
}

func LogoutFromOMC(w http.ResponseWriter, r *http.Request) {
	log.Debug("LogoutFromOMC processing... ")

	// check media type(content type) only support "application/json"
	if !IsVallidContentType(r) {
		log.Debug("Invalid Content-Type")
		services.ResponseUnsupportedMediaType415(w)
		return
	}

	// check extend uri, response 404
	if !IsValidOAuthUri(r) {
		log.Debug("Uri is invalid")
		services.ResponseNotFound404UriNotExist(w, r)
		return
	}

	// error processing ...
	// 401-1 response
	token, ret := globalSession.IsCarriedToken(r)
	if ret == false {
		log.Debug("AccessToken is not carried")
		services.ResponseUnauthorized401AccessTokenNotCarried(w)
		return
	}

	// 401-2 response
	if globalSession.IsValidToken(token) == false {
		log.Debug("AccessToken fails or does not exist")
		services.ResponseUnauthorized401AccessTokenNotExist(w)
		return
	}

	globalSession.EndSession(w, r)
	services.ResponseStatusOK200Null(w)
	return
}

func HandshakeFromOMC(w http.ResponseWriter, r *http.Request) {
	log.Debug("HandshakeFromOMC processing... ")

	// check media type(content type) only support "application/json"
	if !IsVallidContentType(r) {
		log.Debug("Invalid Content-Type")
		services.ResponseUnsupportedMediaType415(w)
		return
	}

	// check extend uri, response 404
	if !IsValidOAuthUri(r) {
		log.Debug("Uri is invalid")
		services.ResponseNotFound404UriNotExist(w, r)
		return
	}

	// error processing ...
	// 401-1 response
	token, ret := globalSession.IsCarriedToken(r)
	if ret == false {
		log.Debug("AccessToken is not carried")
		services.ResponseUnauthorized401AccessTokenNotCarried(w)
		return
	}

	if !globalSession.ShakeSession(token) {
		// 401-2 response
		log.Debug("AccessToken fails or does not exist")
		services.ResponseUnauthorized401AccessTokenNotExist(w)
		return
	}

	// 200 response
	services.ResponseStatusOK200Null(w)
	return
}

var (
	MAX_RMUID_NUM   = config.GetRmUIDMaxNumFromConfig()
	MAX_ALARMID_NUM = config.GetAlarmIDMaxNumFromConfig()
	MAX_PMUID_NUM   = config.GetPmIDMaxNumFromConfig()
	MAX_SUBID_NUM   = config.GetSubIDMaxNumFromConfig()
	MAX_URI_LEN     = config.GetUriMaxLenFromConfig()
	RMUID_REGEXP    = config.GetRmUIDRegexpFromConfig()
)

func CheckParameterName(r *http.Request) []string {
	var errorParams []string
	vars := r.URL.Query()
	for k, v := range vars {
		log.Debug("vars:", k, v)
		if k != "rmUIDs" && k != "fields" {
			errorParams = append(errorParams, k)
		}
	}

	return errorParams
}

func GetRmUIDArr(r *http.Request) []string {
	vars := r.URL.Query()
	rmUIDs, ok := vars["rmUIDs"]
	if !ok {
		log.Debug("rmUIDs is not exist")
		return nil
	}

	var rmUIDValues []string
	for _, r := range rmUIDs {
		if r != "" {
			rmUIDValues = global.MergeStringArr(rmUIDValues, strings.Split(r, `,`))
		}
	}

	return rmUIDValues
}

func GetAttrNameArr(r *http.Request) []string {
	vars := r.URL.Query()
	fields, ok := vars["fields"]
	if !ok {
		log.Debug("attributeNames does not exist")
		return nil
	}
	var attrNames []string
	for _, a := range fields {
		if a != "" {
			attrNames = global.MergeStringArr(attrNames, strings.Split(a, `,`))
		}
	}

	return attrNames
}

func CheckValidRmUID(rmUIDs []string) []string {
	log.Debug("GetLocalRmUID processing... ")

	var invalidRmUIDs []string
	for _, r := range rmUIDs {
		if !global.MatchRmUID(RMUID_REGEXP, r) {
			invalidRmUIDs = append(invalidRmUIDs, r)
		}
	}

	return invalidRmUIDs
}

func CheckLocalRmUID(rmUIDs []string) string {
	log.Debug("GetLocalRmUID processing... ")

	rmUID := config.GetRmUIDFromConfig()
	for _, r := range rmUIDs {
		if r == rmUID {
			return rmUID
		}
	}

	return ""
}

func GetNRMByUri(w http.ResponseWriter, r *http.Request) {
	log.Debug("GetNRMByUri processing... ")

	// response 414-4 uri too long ? (optional)
	// todo ... ?
	if bytes.Count([]byte(r.RequestURI), nil) > MAX_URI_LEN {
		log.Debug("Request Uri too long:", bytes.Count([]byte(r.RequestURI), nil))
		services.ResponseRequestURITooLong414UriTooLong(w)
		return
	}

	// check media type(content type) only support "application/json"
	// response 415-1
	if !IsVallidContentType(r) {
		log.Debug("Invalid Content-Type")
		services.ResponseUnsupportedMediaType415(w)
		return
	}

	// error processing ...
	// 401-1 response
	token, ret := globalSession.IsCarriedToken(r)
	if ret == false {
		log.Debug("AccessToken is not carried")
		services.ResponseUnauthorized401AccessTokenNotCarried(w)
		return
	}

	// 401-2 response
	if globalSession.IsValidToken(token) == false {
		log.Debug("AccessToken fails or does not exist")
		services.ResponseUnauthorized401AccessTokenNotExist(w)
		return
	}
	// response 403 Forbidden, permissions deny
	// todo...
	plist := globalSession.GetPermissionFromSession(token)
	log.Debug("permission list:", plist)
	if len(plist) == 0 || plist[0] == false {
		log.Debug("User permission deny")
		services.ResponseForbidden403NotPermission(w)
		return
	}

	vars := mux.Vars(r)
	qeuryUri := vars["apiCategory"] + "/" + vars["elementTypeValue"] + "/" + vars["objectTypeValue"]
	log.Debug("Get by Uri: ", qeuryUri)

	apiVer := vars["apiVersion"]
	if apiVer != "v1" {
		log.Debug("Uri is invalid")
		services.ResponseNotFound404UriNotExist(w, r)
		return
	}

	// response 406-1
	rmUIDValues := GetRmUIDArr(r)
	if rmUIDValues == nil {
		log.Debug("missing parameter: rmUIDs")
		services.ResponseNotAcceptable406MissingParam(w)
		return
	}

	// response 406-2
	errorParams := CheckParameterName(r)
	if errorParams != nil {
		log.Debug("parameter name error: ", errorParams)
		services.ResponseNotAcceptable406ParamError(w, errorParams)
		return
	}

	// response 400-5
	if len(rmUIDValues) == 0 {
		log.Debug("rmUIDs is wrong or NULL")
		services.ResponseBadRequest400WrongParamValue(w)
		return
	}

	// response 414-1
	if len(rmUIDValues) > MAX_RMUID_NUM {
		log.Debug("rmUID greater than", MAX_RMUID_NUM)
		services.ResponseRequestURITooLong414NRMNumExceed(w, MAX_RMUID_NUM)
		return
	}

	// response 400-1
	// check rmUID is valid
	// todo ...
	invalidRmUIDs := CheckValidRmUID(rmUIDValues)
	if len(invalidRmUIDs) != 0 {
		log.Debug("rmUID is invalid")
		services.ResponseBadRequest400RmUIDsIsInvalid(w, invalidRmUIDs)
		return
	}

	// response 404-2
	rmUID := CheckLocalRmUID(rmUIDValues)
	if rmUID == "" {
		log.Debug("rmUID does not exist")
		services.ResponseNotFound404NRMNotExist(w, rmUIDValues)
		return
	}

	// response 404-1, uri is not exist in map
	attrNames := GetAttrNameArr(r)
	var Oids []string
	Oids = *config.GetOidByFileds(qeuryUri, attrNames, &Oids)
	if len(Oids) == 0 {
		log.Debug("Nothing of config map")
		services.ResponseNotFound404UriNotExist(w, r)
		return
	}

	// response 404-1, uri is not exist in map
	var nameOids []config.NameOid
	nameOids = *config.GetDataOidByFields(qeuryUri, attrNames, &nameOids)
	if len(nameOids) == 0 {
		log.Debug("Nothing of config map")
		services.ResponseNotFound404UriNotExist(w, r)
		return
	}

	result, err2 := g.Default.Get(Oids) // Get() accepts up to g.MAX_OIDS
	if err2 != nil {
		log.Fatalf("Get() err: %v", err2)

	}

	//	var nameValues []config.NameValue
	var nameValue config.NameValue

	nameValues := make(map[string]interface{})
	nameValues["rmUID"] = rmUID
	for i, variable := range result.Variables {
		nameValue.Name = nameOids[i].Name
		log.Debugf("%d: oid: %s name: %s\n", i, variable.Name, nameValue.Name)
		//		if nameOids[i].Oid == variable.Name && global.IsContain(attributeNames, nameValue.Name) {
		if nameOids[i].Oid == variable.Name {
			// the Value of each variable returned by Get() implements
			// interface{}. You could do a type switch...
			switch variable.Type {
			case g.OctetString:
				bytes := variable.Value.([]byte)
				log.Debugf("string: %s\n", string(bytes))
				nameValue.Value = string(bytes)
				nameValues[nameValue.Name] = nameValue.Value
			case g.Integer:
				value := variable.Value.(int)
				log.Debugf("integer: %d\n", value)
				nameValue.Value = strconv.Itoa(value)
				nameValues[nameValue.Name] = nameValue.Value
			case g.IPAddress:
				value := variable.Value.(string)
				log.Debugf("IPAddress: %s\n", variable.Value)
				nameValue.Value = value
				nameValues[nameValue.Name] = nameValue.Value
			default:
				// ... or often you're just interested in numeric values.
				// ToBigInt() will return the Value as a BigInt, for plugging
				// into your calculations.
				log.Debugf("number: %d\n", g.ToBigInt(variable.Value))
			}
		}
	}

	getResponse := services.DataResponse{nameValues}
	services.ResponseWithJson(w, http.StatusOK, getResponse)
}

func RestfulPut(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	queryNo := vars["no"]
	var targetTodo stTodo
	for _, Todo := range TodoList {
		if Todo.No == queryNo {
			targetTodo = Todo
		}
	}
	response := ApiResponse{"200", targetTodo}
	services.ResponseWithJson(w, http.StatusOK, response)
}

func RestfulHead(w http.ResponseWriter, r *http.Request) {
	var targetTodo stTodo

	response := ApiResponse{"200", targetTodo}
	services.ResponseWithJson(w, http.StatusOK, response)

}

func RemoveElement(TodoList []stTodo, No string) stTodo {
	//	var targetTodo stTodo
	j := 0
	if len(TodoList) == 0 {
		return TodoList[j]
	}

	for i := 0; i < len(TodoList); i++ {
		if TodoList[i].No != No {
			if i != j {
				TodoList[i], TodoList[j] = TodoList[j], TodoList[i]
			}
			j++
		}
	}
	log.Debug(TodoList[j].No, TodoList[j].Item)
	return TodoList[j]
}

func RestfulDelete(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	queryNo := vars["no"] //获取No

	log.Debug("RestfulDelete processing... ")

	targetTodo := RemoveElement(TodoList, queryNo)
	response := ApiResponse{"200", targetTodo}
	services.ResponseWithJson(w, http.StatusOK, response)
}

func RestfulOptions(w http.ResponseWriter, r *http.Request) {
	var targetTodo stTodo

	response := ApiResponse{"200", targetTodo}
	services.ResponseWithJson(w, http.StatusOK, response)

}

func RestfulTrace(w http.ResponseWriter, r *http.Request) {
	var targetTodo stTodo

	response := ApiResponse{"200", targetTodo}
	services.ResponseWithJson(w, http.StatusOK, response)

}

func RestfulPatch(w http.ResponseWriter, r *http.Request) {
	var targetTodo stTodo

	response := ApiResponse{"200", targetTodo}
	services.ResponseWithJson(w, http.StatusOK, response)

}
