package httper

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"github.com/astaxie/beego/context"
	"go.uber.org/zap"
)

var (
	Debug    = false
	DebugLog *zap.Logger
)

func FillRequireParams(ctx *context.Context, param interface{}, logID string) {
	v := reflect.ValueOf(param)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	if v.Kind() != reflect.Struct {
		SystemError("query is not a struct")
	}

	numField := v.NumField()
	for i := 0; i < numField; i++ {
		fillParams(ctx, v.Type().Field(i), v.Field(i))
	}

	if Debug && DebugLog != nil {
		data, _ := json.Marshal(param)
		DebugLog.Debug(logID, zap.String("request", string(data)))
	}
}

func furtherFillRequireParams(ctx *context.Context, param interface{}) {
	v := reflect.ValueOf(param)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		SystemError("query is not a struct")
	}

	numField := v.NumField()
	for i := 0; i < numField; i++ {
		fillParams(ctx, v.Type().Field(i), v.Field(i))
	}
}

func fillParams(ctx *context.Context, field reflect.StructField, val reflect.Value) {
	//fmt.Println(ctx.Input.Context.Request.Header)
	switch field.Tag.Get("in") {
	case "header":
		fillHeaderParams(ctx, field, val)
	case "body":
		fillBodyParams(ctx, field, val)
	case "path":
		fillPathParams(ctx, field, val)
	case "formData":
		fallthrough
	case "query":
		fillQueryParams(ctx, field, val)
	default:
		if !val.IsValid() || !val.CanAddr() || !val.CanSet() {
			SystemError("value can not set")
		}

		furtherFillRequireParams(ctx, val.Addr().Interface())
	}
}

func fillBodyParams(ctx *context.Context, field reflect.StructField, val reflect.Value) {
	if !val.IsValid() || !val.CanAddr() || !val.CanSet() {
		SystemError("value can not set")
	}

	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}

	body := ctx.Input.RequestBody
	switch field.Tag.Get("decode") {
	case "base64":
		body, _ = base64.StdEncoding.DecodeString(string(body))
	}
	if err := json.Unmarshal(body, val.Addr().Interface()); err != nil {
		BadParamer(fmt.Sprintf("%s is not json error:%s", "body", err))
	}
	CheckBodyFiled(val.Addr().Interface())
}

func CheckBodyFiled(param interface{}, logID ...interface{}) {
	v := reflect.ValueOf(param)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		SystemError("body is not a struct")
	}
	numField := v.NumField()
	for i := 0; i < numField; i++ {
		if v.Field(i).Kind() == reflect.Struct { //递归检查
			CheckBodyFiled(v.Field(i).Addr().Interface())
		}

		field := v.Type().Field(i)
		val := v.Field(i)
		if reflect.DeepEqual(val.Interface(), reflect.Zero(val.Type()).Interface()) {
			if field.Tag.Get("required") == "true" {
				jsonTag := field.Tag.Get("json")
				if jsonTag == "" {
					jsonTag = field.Name
				}

				def := field.Tag.Get("default")
				fillValue(v.Field(i), def, true, jsonTag)
			}
		}
	}
}

func fillHeaderParams(ctx *context.Context, field reflect.StructField, val reflect.Value) {
	jsonTag := field.Tag.Get("json")
	if jsonTag == "" {
		// 没有json标记，取变量的名称
		jsonTag = field.Name
	}

	var v string
	if v = ctx.Input.Header(jsonTag); v == "" {
		// 尝试将第一个字母转换为大写 测试环境使用 beego Swagger的bug
		firstUpperJsonTag := strings.ToUpper(jsonTag[0:1]) + string(jsonTag[1:])
		if v = ctx.Input.Header(firstUpperJsonTag); v == "" {
			v = field.Tag.Get("default")
		}
	}

	required := field.Tag.Get("required") == "true"
	if v == "" && required {
		BadParamer(fmt.Sprintf("%s is not exists in header", jsonTag))
	}
	fmt.Printf("header:%v\n", v)

	fillValue(val, v, required, jsonTag)
}

func fillPathParams(ctx *context.Context, field reflect.StructField, val reflect.Value) {
	jsonTag := field.Tag.Get("json")
	if jsonTag == "" {
		// 没有json标记，取变量的名称
		jsonTag = field.Name
	}

	var v string
	if v = ctx.Input.Param(fmt.Sprintf(":%s", jsonTag)); v == "" {
		v = field.Tag.Get("default")
	}

	required := field.Tag.Get("required") == "true"
	if v == "" && required {
		BadParamer(fmt.Sprintf("%s is not exists in path", jsonTag))
	}

	fillValue(val, v, required, jsonTag)
}

func fillQueryParams(ctx *context.Context, field reflect.StructField, val reflect.Value) {
	jsonTag := field.Tag.Get("json")
	if jsonTag == "" {
		// 没有json标记，取变量的名称
		jsonTag = field.Name
	}

	var v string
	if v = ctx.Input.Query(jsonTag); v == "" {
		v = field.Tag.Get("default")
	}

	required := field.Tag.Get("required") == "true"
	if v == "" && required {
		BadParamer(fmt.Sprintf("%s is not exists in query", jsonTag))
	}

	fillValue(val, v, required, jsonTag)
}

func fillValue(val reflect.Value, requestVal string, required bool, jsonTag string) {
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}

	kind := val.Kind()
	switch {
	case kind == reflect.Uint || kind == reflect.Uint8 || kind == reflect.Uint16 || kind == reflect.Uint32 || kind == reflect.Uint64:
		if requestVal == "" && !required {
			requestVal = "0"
		}
		v, err := strconv.ParseUint(requestVal, 10, 64)
		if err != nil {
			BadParamer(fmt.Sprintf("%s is not uint", jsonTag))
		}
		if !val.IsValid() || !val.CanSet() {
			SystemError("value can not set")
		}
		val.SetUint(v)
	case kind == reflect.Int || kind == reflect.Int64:
		if requestVal == "" && !required {
			requestVal = "0"
		}
		v, err := strconv.ParseInt(requestVal, 10, 64)
		if err != nil {
			BadParamer(fmt.Sprintf("%s is not uint", jsonTag))
		}
		if !val.IsValid() || !val.CanSet() {
			SystemError("value can not set")
		}
		val.SetInt(v)
	case kind == reflect.Float64 || kind == reflect.Float32:
		if requestVal == "" && !required {
			requestVal = "0"
		}
		v, err := strconv.ParseFloat(requestVal, 64)
		if err != nil {
			BadParamer(fmt.Sprintf("%s is not uint", jsonTag))
		}
		if !val.IsValid() || !val.CanSet() {
			SystemError("value can not set")
		}
		val.SetFloat(v)
	case kind == reflect.String:
		val.SetString(requestVal)
	case kind == reflect.Bool:
		if requestVal == "" {
			requestVal = "false"
		}
		v, err := strconv.ParseBool(requestVal)
		if err != nil {
			BadParamer(fmt.Sprintf("%s is not bool", jsonTag))
		}
		val.SetBool(v)
	default:
		BadParamer("type is not support")
	}
}
