/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package schema

import (
	"fmt"
	"github.com/go-chassis/openlog"
	"strings"

	"github.com/go-chassis/go-chassis/v2/core/registry"
)

const (
	InQuery = 0
	InBody  = 1
)

//InterfaceSchema is a struct
type InterfaceSchema struct {
	SvcName      string
	JavaClsName  string
	BasePath     string
	Version      string
	AppId        string
	ServiceId    string
	methordArray []DefMethod
}

//DefMethod is a struct
type DefMethod struct {
	ownerSvc string
	Path     string //  样例: /test/StringArray 需要是包含basepath的URL
	OperaID  string
	Paras    []MethParam             //key 参数名称
	Verb     string                  // get post ...
	Responds map[string]*MethRespond //key 为返回码 200 ,404...
}

//GetRspSchema is a method to get response schema
func (this *DefMethod) GetRspSchema(status int) *MethRespond {
	strStatus := fmt.Sprintf("%d", status)
	if _, ok := this.Responds[strStatus]; ok {
		return this.Responds[strStatus]
	} else {
		return nil
	}
}

//GetParamNameAndWhere is a method to get parameter name
func (this *DefMethod) GetParamNameAndWhere(indx int) (string, int) {
	for _, v := range this.Paras {
		if v.Indx == indx {
			//return v.name, v.where
			if strings.EqualFold(v.Where, "query") {
				return v.Name, InQuery
			} else {
				return v.Name, InBody
			}
		}
	}
	return "", InQuery
}

//GetParamSchema is a method to get parameter schema
func (this *DefMethod) GetParamSchema(indx int) *MethParam {
	for _, v := range this.Paras {
		if v.Indx == indx {
			return &v
		}
	}
	return nil
}

//MethRespond is a struct
type MethRespond struct {
	Status string // 200 404...
	DType  string
	ObjRef DefType
}

//MethParam is a struct
type MethParam struct {
	Name            string            //参数名称
	Dtype           string            //参数类型
	AdditionalProps map[string]string //附加参数，如果是Dtype是map，则在这里定义value类型
	Items           map[string]string //当Dtype为array时使用
	Required        bool              //是否必需
	Where           string            //存储位置  query or body
	Indx            int
	ObjRef          DefType
}

//DefType 契约definitions字段里定义的类型定义
type DefType struct {
	typeName   string
	dType      string
	JvmClsName string
	fileds     map[string]DefField
}

//DefField is a struct
type DefField struct {
	dType   string
	formate string
}

//GetDefTypeFromDef is a function to get defintion type
func GetDefTypeFromDef(defs map[string]registry.Definition, ref string) DefType {
	def := DefType{}
	names := strings.Split(ref, "/")
	def.typeName = names[len(names)-1]
	if sDef, ok := defs[def.typeName]; ok {
		def.dType = sDef.Types
		def.JvmClsName = sDef.XJavaClass
	}
	return def
}

//CovertSwaggerMethordToLocalMethord is a function to convert swagger method to local method
func CovertSwaggerMethordToLocalMethord(schema *registry.SchemaContent, src *registry.MethodInfo, dst *DefMethod) {
	dst.OperaID = src.OperationID
	tmpParas := make([]MethParam, len(src.Parameters))
	i := 0
	for _, para := range src.Parameters {
		var defPara MethParam
		defPara.Name = para.Name
		if para.Type == "" {
			if para.Schema.Type != "" {
				defPara.Dtype = para.Schema.Type
			} else {
				defPara.Dtype = "object"
				if para.Schema.Reference != "" {
					defPara.ObjRef = GetDefTypeFromDef(schema.Definition, para.Schema.Reference)
				}
			}
		} else {
			defPara.Dtype = para.Type
		}
		defPara.Required = para.Required
		defPara.Where = para.In
		defPara.Indx = i
		tmpParas[i] = defPara
		i++
	}
	dst.Paras = tmpParas
	tmpRsps := make(map[string]*MethRespond)
	for key, rsp := range src.Response {
		var defRsp MethRespond
		if dtype, ok := rsp.Schema["type"]; ok {
			defRsp.DType = dtype
		} else {
			defRsp.DType = "object"
			if dRef, ok := rsp.Schema["$ref"]; ok {
				defRsp.ObjRef = GetDefTypeFromDef(schema.Definition, dRef)
			}
		}
		defRsp.Status = key
		tmpRsps[key] = &defRsp
	}
	dst.Responds = tmpRsps
}

//GetSvcByInterface is a function to get service by interface name
func GetSvcByInterface(interfaceName string) *registry.MicroService {
	value, ok := svcToInterfaceCache.Get(interfaceName)
	if !ok || value == nil {
		openlog.Info(fmt.Sprintf("Get svc from remote, interface: %s", interfaceName))
		svc := registry.DefaultContractDiscoveryService.GetMicroServicesByInterface(interfaceName)
		if svc != nil {
			svcKey := strings.Join([]string{svc[0].ServiceName, svc[0].Version, svc[0].AppID}, "/")
			openlog.Info(fmt.Sprintf("Cached svc [%s] for interface %s", svcKey, interfaceName))
			svcToInterfaceCache.Set(interfaceName, svc[0], 0)
			refresher.Add(newInterfaceJob(interfaceName))
		} else {
			return nil
		}
		value, ok = svcToInterfaceCache.Get(interfaceName)
	}
	if value != nil {
		if service, ok2 := value.(*registry.MicroService); ok2 {
			return service
		}
	}
	return nil
}

//GetSvcNameByInterface is a function to get service name by interface
func GetSvcNameByInterface(interfaceName string) string {
	svc := registry.DefaultContractDiscoveryService.GetMicroServicesByInterface(interfaceName)
	for _, v := range svc {
		return v.ServiceName
	}
	return ""
}

//GetMethodByInterface is a function to get method from interface name
func GetMethodByInterface(interfaceName string, operateID string) *DefMethod {
	var meth DefMethod

	schema := registry.DefaultContractDiscoveryService.GetSchemaContentByInterface(interfaceName)
	for path, pathSchema := range schema.Paths {
		for verb, m := range pathSchema {
			if strings.EqualFold(m.OperationID, operateID) {
				meth.Verb = strings.ToUpper(verb)
				meth.Path = schema.BasePath + path
				meth.OperaID = operateID
				CovertSwaggerMethordToLocalMethord(&schema, &m, &meth)
				return &meth
			}
		}
	}
	return nil
}

//GetSchemaMethodBySvcURL is a function to get schema method from URl
func GetSchemaMethodBySvcURL(svcName string, env string, ver string, app string, verb string, url string) (*registry.SchemaContent, *DefMethod) {
	schemas := registry.DefaultContractDiscoveryService.GetSchemaContentByServiceName(svcName, ver, app, env)
	var curMethrod *DefMethod
	var curSchema *registry.SchemaContent
	for _, v := range schemas {
		curSchema = v
		methord := GetMethodInfoSchemaByURL(v, verb, url)
		if methord != nil {
			if curMethrod == nil {
				curMethrod = methord
			} else {
				if len(curMethrod.Path) < len(methord.Path) {
					curMethrod = methord
				}
			}
		}
	}
	return curSchema, curMethrod
}

//GetMethodInfoSchemaByURL is a function to get method info schema from URl
func GetMethodInfoSchemaByURL(schema *registry.SchemaContent, verb string, url string) *DefMethod {
	var curMax = 0
	basePath := schema.BasePath
	var method *registry.MethodInfo
	var path string
	for key, v := range schema.Paths {
		if strings.HasPrefix(url, basePath+key) {
			if tmp, ok := v[verb]; ok {
				if len(key) > curMax {
					curMax = len(key)
					method = &tmp
					path = key
				}
			}
		}
	}
	if method != nil {
		tmpMeth := &DefMethod{}
		tmpMeth.Path = basePath + path
		tmpMeth.Verb = verb
		CovertSwaggerMethordToLocalMethord(schema, method, tmpMeth)
		return tmpMeth
	} else {
		return nil
	}
}
